diff --git a/app/build.gradle b/app/build.gradle index e843863b82..401db3a2b4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -117,6 +117,7 @@ android { flavorDimensions = ["standard"] productFlavors { full { + getIsDefault().set(true) applicationId "info.nightscout.androidaps" dimension "standard" resValue "string", "app_name", "AAPS" diff --git a/app/src/main/kotlin/app/aaps/activities/MyPreferenceFragment.kt b/app/src/main/kotlin/app/aaps/activities/MyPreferenceFragment.kt index 530b990657..28075f34f3 100644 --- a/app/src/main/kotlin/app/aaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/kotlin/app/aaps/activities/MyPreferenceFragment.kt @@ -43,7 +43,8 @@ import app.aaps.plugins.configuration.maintenance.MaintenancePlugin import app.aaps.plugins.constraints.safety.SafetyPlugin import app.aaps.plugins.insulin.InsulinOrefFreePeakPlugin import app.aaps.plugins.main.general.smsCommunicator.SmsCommunicatorPlugin -import app.aaps.plugins.main.general.wear.WearPlugin +import app.aaps.plugins.sync.garmin.GarminPlugin +import app.aaps.plugins.sync.wear.WearPlugin import app.aaps.plugins.sensitivity.SensitivityAAPSPlugin import app.aaps.plugins.sensitivity.SensitivityOref1Plugin import app.aaps.plugins.sensitivity.SensitivityWeightedAveragePlugin @@ -128,6 +129,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang @Inject lateinit var nsSettingStatus: NSSettingsStatus @Inject lateinit var openHumansUploaderPlugin: OpenHumansUploaderPlugin @Inject lateinit var diaconnG8Plugin: DiaconnG8Plugin + @Inject lateinit var garminPlugin: GarminPlugin override fun onAttach(context: Context) { AndroidSupportInjection.inject(this) @@ -229,6 +231,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang addPreferencesFromResource(app.aaps.plugins.configuration.R.xml.pref_datachoices, rootKey) addPreferencesFromResourceIfEnabled(maintenancePlugin, rootKey) addPreferencesFromResourceIfEnabled(openHumansUploaderPlugin, rootKey) + addPreferencesFromResourceIfEnabled(garminPlugin, rootKey) } initSummary(preferenceScreen, pluginId != -1) preprocessPreferences() diff --git a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt index 10b296c758..6ea627c117 100644 --- a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt +++ b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt @@ -22,11 +22,12 @@ import app.aaps.plugins.insulin.InsulinOrefRapidActingPlugin import app.aaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin import app.aaps.plugins.main.general.actions.ActionsPlugin import app.aaps.plugins.main.general.food.FoodPlugin +import app.aaps.plugins.sync.garmin.GarminPlugin import app.aaps.plugins.main.general.overview.OverviewPlugin import app.aaps.plugins.main.general.persistentNotification.PersistentNotificationPlugin import app.aaps.plugins.main.general.smsCommunicator.SmsCommunicatorPlugin import app.aaps.plugins.main.general.themes.ThemeSwitcherPlugin -import app.aaps.plugins.main.general.wear.WearPlugin +import app.aaps.plugins.sync.wear.WearPlugin import app.aaps.plugins.main.iob.iobCobCalculator.IobCobCalculatorPlugin import app.aaps.plugins.main.profile.ProfilePlugin import app.aaps.plugins.sensitivity.SensitivityAAPSPlugin @@ -309,12 +310,6 @@ abstract class PluginsListModule { @IntKey(320) abstract fun bindFoodPlugin(plugin: FoodPlugin): PluginBase - @Binds - @AllConfigs - @IntoMap - @IntKey(330) - abstract fun bindWearPlugin(plugin: WearPlugin): PluginBase - @Binds @AllConfigs @IntoMap @@ -340,16 +335,28 @@ abstract class PluginsListModule { abstract fun bindXdripPlugin(plugin: XdripPlugin): PluginBase @Binds - @AllConfigs + @NotNSClient @IntoMap @IntKey(366) + abstract fun bindsOpenHumansPlugin(plugin: OpenHumansUploaderPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(367) + abstract fun bindWearPlugin(plugin: WearPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(368) abstract fun bindDataBroadcastPlugin(plugin: DataBroadcastPlugin): PluginBase @Binds - @NotNSClient + @AllConfigs @IntoMap - @IntKey(368) - abstract fun bindsOpenHumansPlugin(plugin: OpenHumansUploaderPlugin): PluginBase + @IntKey(369) + abstract fun bindGarminPlugin(plugin: GarminPlugin): PluginBase @Binds @AllConfigs diff --git a/app/src/main/kotlin/app/aaps/implementations/UiInteractionImpl.kt b/app/src/main/kotlin/app/aaps/implementations/UiInteractionImpl.kt index 774c88d9c0..5672ea7650 100644 --- a/app/src/main/kotlin/app/aaps/implementations/UiInteractionImpl.kt +++ b/app/src/main/kotlin/app/aaps/implementations/UiInteractionImpl.kt @@ -14,6 +14,7 @@ import app.aaps.activities.PreferencesActivity import app.aaps.core.interfaces.notifications.Notification import app.aaps.core.interfaces.nsclient.NSAlarm import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.EventDismissNotification import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.main.events.EventNewNotification import app.aaps.core.ui.toast.ToastUtils @@ -169,6 +170,10 @@ class UiInteractionImpl @Inject constructor( } } + override fun dismissNotification(id: Int) { + rxBus.send(EventDismissNotification(id)) + } + override fun addNotification(id: Int, text: String, level: Int) { rxBus.send(EventNewNotification(Notification(id, text, level))) } diff --git a/build.gradle b/build.gradle index 062178e7fd..44a644c1b0 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ buildscript { preferencektx_version = '1.2.1' commonslang3_version = '3.13.0' commonscodec_version = '1.16.0' - guava_version = '32.1.2-jre' + guava_version = '32.1.3-jre' jodatime_version = '2.12.5' work_version = '2.8.1' tink_version = '1.10.0' @@ -80,7 +80,7 @@ buildscript { plugins { // Test Gradle build, keep disabled under normal circumstances // id "com.osacky.doctor" version "0.8.1" - id "org.jlleitschuh.gradle.ktlint" version "11.6.0" + id "org.jlleitschuh.gradle.ktlint" version "11.6.1" // Aggregates and/or logs Jacoco test coverage to the Gradle build log //id 'org.barfuin.gradle.jacocolog' version '3.1.0' id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false @@ -97,6 +97,7 @@ allprojects { kotlinOptions { freeCompilerArgs = [ '-opt-in=kotlin.RequiresOptIn', + '-opt-in=kotlin.ExperimentalUnsignedTypes', '-Xjvm-default=all' //Support @JvmDefault ] jvmTarget = "11" diff --git a/core/graphview/src/main/java/com/jjoe64/graphview/series/BaseSeries.java b/core/graphview/src/main/java/com/jjoe64/graphview/series/BaseSeries.java index bd7cb5081a..d5af6a5449 100644 --- a/core/graphview/src/main/java/com/jjoe64/graphview/series/BaseSeries.java +++ b/core/graphview/src/main/java/com/jjoe64/graphview/series/BaseSeries.java @@ -140,7 +140,7 @@ public abstract class BaseSeries implements Series * @return the highest y value, or 0 if there is no data */ public double getHighestValueY() { - if (mData.isEmpty()) return 0d; + if (mData.isEmpty()) return 100d; double h = mData.get(0).getY(); for (int i = 1; i < mData.size(); i++) { double c = mData.get(i).getY(); diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/logging/LTag.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/logging/LTag.kt index d516a7822f..69a38331b7 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/logging/LTag.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/logging/LTag.kt @@ -12,6 +12,7 @@ enum class LTag(val tag: String, val defaultValue: Boolean = true, val requiresR DATABASE("DATABASE"), DATATREATMENTS("DATATREATMENTS"), EVENTS("EVENTS", defaultValue = false, requiresRestart = true), + GARMIN("GARMIN"), GLUCOSE("GLUCOSE", defaultValue = false), HTTP("HTTP"), LOCATION("LOCATION"), diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/maintenance/PrefFileListProvider.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/maintenance/PrefFileListProvider.kt index ed0f3745cc..263cf8e052 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/maintenance/PrefFileListProvider.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/maintenance/PrefFileListProvider.kt @@ -1,6 +1,7 @@ package app.aaps.core.interfaces.maintenance import app.aaps.core.interfaces.rx.weardata.CwfData +import app.aaps.core.interfaces.rx.weardata.CwfFile import java.io.File interface PrefFileListProvider { @@ -13,7 +14,7 @@ interface PrefFileListProvider { fun newExportCsvFile(): File fun newCwfFile(filename: String, withDate: Boolean = true): File fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList - fun listCustomWatchfaceFiles(): MutableList + fun listCustomWatchfaceFiles(): MutableList fun checkMetadata(metadata: Map): Map fun formatExportedAgo(utcTime: String): String } \ No newline at end of file diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/events/EventMobileDataToWear.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/events/EventMobileDataToWear.kt index 6f3ef85b9b..05a7bfcec8 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/events/EventMobileDataToWear.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/events/EventMobileDataToWear.kt @@ -2,4 +2,4 @@ package app.aaps.core.interfaces.rx.events import app.aaps.core.interfaces.rx.weardata.EventData -class EventMobileDataToWear(val payload: EventData.ActionSetCustomWatchface) : Event() \ No newline at end of file +class EventMobileDataToWear(val payload: ByteArray) : Event() \ No newline at end of file diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/CustomWatchfaceFormat.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/CustomWatchfaceFormat.kt index a3a9ba6438..3ef2d4acde 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/CustomWatchfaceFormat.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/CustomWatchfaceFormat.kt @@ -12,6 +12,7 @@ import com.caverock.androidsvg.SVG import kotlinx.serialization.Serializable import org.json.JSONObject import java.io.BufferedOutputStream +import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream @@ -135,9 +136,15 @@ data class ResData(val value: ByteArray, val format: ResFormat) { typealias CwfResDataMap = MutableMap typealias CwfMetadataMap = MutableMap fun CwfResDataMap.isEquals(dataMap: CwfResDataMap) = (this.size == dataMap.size) && this.all { (key, resData) -> dataMap[key]?.value.contentEquals(resData.value) == true } - @Serializable -data class CwfData(val json: String, var metadata: CwfMetadataMap, val resDatas: CwfResDataMap) +data class CwfData(val json: String, var metadata: CwfMetadataMap, val resDatas: CwfResDataMap) { + fun simplify(): CwfData? = resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.let { + val simplifiedDatas: CwfResDataMap = mutableMapOf() + simplifiedDatas[ResFileMap.CUSTOM_WATCHFACE.fileName] = it + CwfData(json, metadata, simplifiedDatas) + } +} +data class CwfFile(val cwfData: CwfData, val zipByteArray: ByteArray) enum class CwfMetadataKey(val key: String, @StringRes val label: Int, val isPref: Boolean) { @@ -287,11 +294,11 @@ class ZipWatchfaceFormat { const val CWF_EXTENTION = ".zip" const val CWF_JSON_FILE = "CustomWatchface.json" - fun loadCustomWatchface(zipInputStream: ZipInputStream, zipName: String, authorization: Boolean): CwfData? { + fun loadCustomWatchface(byteArray: ByteArray, zipName: String, authorization: Boolean): CwfFile? { var json = JSONObject() var metadata: CwfMetadataMap = mutableMapOf() val resDatas: CwfResDataMap = mutableMapOf() - + val zipInputStream = byteArrayToZipInputStream(byteArray) try { var zipEntry: ZipEntry? = zipInputStream.nextEntry while (zipEntry != null) { @@ -325,7 +332,7 @@ class ZipWatchfaceFormat { // Valid CWF file must contains a valid json file with a name within metadata and a custom watchface image return if (metadata.containsKey(CwfMetadataKey.CWF_NAME) && resDatas.containsKey(ResFileMap.CUSTOM_WATCHFACE.fileName)) - CwfData(json.toString(4), metadata, resDatas) + CwfFile(CwfData(json.toString(4), metadata, resDatas), byteArray) else null @@ -369,5 +376,10 @@ class ZipWatchfaceFormat { } return metadata } + + fun byteArrayToZipInputStream(byteArray: ByteArray): ZipInputStream { + val byteArrayInputStream = ByteArrayInputStream(byteArray) + return ZipInputStream(byteArrayInputStream) + } } } \ No newline at end of file diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt index e20a0796b9..744712d139 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/rx/weardata/EventData.kt @@ -292,10 +292,9 @@ sealed class EventData : Event() { } @Serializable - data class ActionSetCustomWatchface( - val customWatchfaceData: CwfData - ) : EventData() - + data class ActionSetCustomWatchface(val customWatchfaceData: CwfData) : EventData() + @Serializable + data class ActionUpdateCustomWatchface(val customWatchfaceData: CwfData) : EventData() @Serializable data class ActionrequestCustomWatchface(val exportFile: Boolean) : EventData() diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/ui/UiInteraction.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/ui/UiInteraction.kt index f19feb9894..e56d83adce 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/ui/UiInteraction.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/ui/UiInteraction.kt @@ -68,6 +68,7 @@ interface UiInteraction { fun runCareDialog(fragmentManager: FragmentManager, options: EventType, @StringRes event: Int) + fun dismissNotification(id: Int) fun addNotification(id: Int, text: String, level: Int) fun addNotificationValidFor(id: Int, text: String, level: Int, validMinutes: Int) fun addNotificationWithSound(id: Int, text: String, level: Int, @RawRes soundId: Int?) diff --git a/core/interfaces/src/main/res/values-nb-rNO/strings.xml b/core/interfaces/src/main/res/values-nb-rNO/strings.xml index f0d8eeb0e6..4f650e3bf1 100644 --- a/core/interfaces/src/main/res/values-nb-rNO/strings.xml +++ b/core/interfaces/src/main/res/values-nb-rNO/strings.xml @@ -51,7 +51,7 @@ Vis Gj. snitt Delta Vis telefonbatteri Vis riggens batteri - Vis basalrate + Vis basaldose Vis loop status Vis BS Vis BGI @@ -75,7 +75,7 @@ Gj.snitt BS-endring (15min) Telefonbatteri (%) Rig-batteri (%) - Basalrate + Basaldose BGI verdi Tid (TT:MM eller TT:MM:SS) Time (TT) diff --git a/core/main/android_module_dependencies.gradle b/core/main/android_module_dependencies.gradle index d8717e8cae..8ccf62d869 100644 --- a/core/main/android_module_dependencies.gradle +++ b/core/main/android_module_dependencies.gradle @@ -3,6 +3,7 @@ android { flavorDimensions = ["standard"] productFlavors { full { + getIsDefault().set(true) dimension "standard" } pumpcontrol { diff --git a/core/main/test_dependencies.gradle b/core/main/test_dependencies.gradle index c0ca1a6673..800cb42b2e 100644 --- a/core/main/test_dependencies.gradle +++ b/core/main/test_dependencies.gradle @@ -13,7 +13,6 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.test.ext:junit-ktx:$androidx_junit_version" androidTestImplementation "androidx.test:rules:$androidx_rules_version" - androidTestImplementation "org.junit.jupiter:junit-jupiter-api:$junit_jupiter_version" androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' } diff --git a/core/ui/src/main/res/values-nb-rNO/strings.xml b/core/ui/src/main/res/values-nb-rNO/strings.xml index 1612d72d0a..02b5024141 100644 --- a/core/ui/src/main/res/values-nb-rNO/strings.xml +++ b/core/ui/src/main/res/values-nb-rNO/strings.xml @@ -74,8 +74,8 @@ Målverdi Insulinets virkningstid (DIA) Insulin-karbohydratfaktor (IK) - Insulin sensitivitetsfaktor (ISF) - Basalrate + Insulinsensitivitetsfaktor (ISF) + Basaldose Blodsukkermål g % @@ -155,7 +155,7 @@ Login Prime/fylling Insulin - Avbryt midlertidig målverdi + Avbryt midlertidig mål Lukket Loop Åpen Loop Stopp ved lavt BS @@ -193,7 +193,7 @@ %1$d min - Careportal + Helseportal BS-kontroll Manuelt BS eller kalibrering Melding @@ -294,19 +294,19 @@ AVBRYT BOLUS AVBRYT FORLENGET BOLUS AVBRYT MIDL. MÅL - CAREPORTAL + HELSEPORTAL BYTTE SLANGESETT BYTTE RESERVOAR KALIBRERING PRIME BOLUS BEHANDLING - CAREPORTAL NS OPPDATERING + HELSEPORTAL NS-OPPDATERING PROFILBYTTE NS OPPDATERING BEHANDLINGER NS OPPDATERING OPPDATER MIDL. MÅL NS AUTOMASJON FJERNET BS FJERNET - CAREPORTAL FJERNET + HELSEPORTAL FJERNET BOLUS FJERNET KARBO FJERNET MIDL. MÅL FJERNET @@ -363,7 +363,7 @@ Øvre grense for midl. mål Midl. målverdi Profil DIA verdi - Profil insulinfølsomhet + Profilens insulinfølsomhet Maksimal profil basalverdi Nåværende basalverdi Profil karbohydratfaktor (IK) @@ -389,7 +389,7 @@ MIDL. BASAL %1$.2f E/t %2$d min MIDL. BASAL %1$d E/t %2$d min INSIGHT SET TBR OVER NOTIFIKASJON - READSTATUS %1$s + LESESTATUS %1$s Oppretthold tilkobling. Status er ikke oppdatert. Oppretthold tilkobling. Basal er ikke oppdatert. SMS @@ -486,7 +486,7 @@ Insulinresistent voksen Graviditet Velg pasienttype for oppsett av sikkerhetsgrenser - Maks tillatt bolus [U] + Maks tillatt bolus [E] Maks tillatt karbohydrater [g] Pasienttype @@ -561,9 +561,9 @@ Mangler SMS-tillatelse - Hvordan hindre at appen stenges? + Ikke steng appen min? Opplast av krasjlogger er deaktivert! - \n\nDokumentasjon:\nhttps://androidaps.readthedocs.io\n\nfacebook:\nhttps://www.facebook.com/groups/AndroidAPSUsers + \n\nDokumentasjon:\nhttps://androidaps.readthedocs.io\n\nFacebook:\nhttps://www.facebook.com/groups/AndroidAPSUsers %1$d dag %1$d dager diff --git a/core/utils/src/main/res/values/keys.xml b/core/utils/src/main/res/values/keys.xml index f8e845faa9..b98cdfe2c0 100644 --- a/core/utils/src/main/res/values/keys.xml +++ b/core/utils/src/main/res/values/keys.xml @@ -116,6 +116,9 @@ wearwizard_cob wearwizard_iob wear_custom_watchface_autorization + wear_cwf_watchface_name + wear_cwf_author_version + wear_cwf_filename ObjectivesbgIsAvailableInNS ObjectivespumpStatusIsAvailableInNS statuslights_cage_warning diff --git a/database/entities/src/main/kotlin/app/aaps/database/entities/UserEntry.kt b/database/entities/src/main/kotlin/app/aaps/database/entities/UserEntry.kt index 98b04d65ac..d336cbfd11 100644 --- a/database/entities/src/main/kotlin/app/aaps/database/entities/UserEntry.kt +++ b/database/entities/src/main/kotlin/app/aaps/database/entities/UserEntry.kt @@ -187,7 +187,9 @@ data class UserEntry( Overview, //From OverViewPlugin Stats, //From Stat Activity Aaps, // MainApp + GarminDevice, Unknown //if necessary + , ; companion object { diff --git a/database/impl/schemas/app.aaps.database.impl.AppDatabase/22.json b/database/impl/schemas/app.aaps.database.impl.AppDatabase/22.json new file mode 100644 index 0000000000..93af25de94 --- /dev/null +++ b/database/impl/schemas/app.aaps.database.impl.AppDatabase/22.json @@ -0,0 +1,3605 @@ +{ + "formatVersion": 1, + "database": { + "version": 22, + "identityHash": "09121464fb795b3c37bb1c2c2c3ea481", + "entities": [ + { + "tableName": "apsResults", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `algorithm` TEXT NOT NULL, `glucoseStatusJson` TEXT NOT NULL, `currentTempJson` TEXT NOT NULL, `iobDataJson` TEXT NOT NULL, `profileJson` TEXT NOT NULL, `autosensDataJson` TEXT, `mealDataJson` TEXT NOT NULL, `isMicroBolusAllowed` INTEGER, `resultJson` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `apsResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "algorithm", + "columnName": "algorithm", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseStatusJson", + "columnName": "glucoseStatusJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "currentTempJson", + "columnName": "currentTempJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "iobDataJson", + "columnName": "iobDataJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileJson", + "columnName": "profileJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "autosensDataJson", + "columnName": "autosensDataJson", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mealDataJson", + "columnName": "mealDataJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isMicroBolusAllowed", + "columnName": "isMicroBolusAllowed", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "resultJson", + "columnName": "resultJson", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_apsResults_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResults_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_apsResults_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResults_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "apsResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "boluses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `amount` REAL NOT NULL, `type` TEXT NOT NULL, `notes` TEXT, `isBasalInsulin` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT, `insulinEndTime` INTEGER, `peak` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isBasalInsulin", + "columnName": "isBasalInsulin", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_boluses_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_boluses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_boluses_temporaryId", + "unique": false, + "columnNames": [ + "temporaryId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_temporaryId` ON `${TABLE_NAME}` (`temporaryId`)" + }, + { + "name": "index_boluses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_boluses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_boluses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_boluses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_boluses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_boluses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "bolusCalculatorResults", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `targetBGLow` REAL NOT NULL, `targetBGHigh` REAL NOT NULL, `isf` REAL NOT NULL, `ic` REAL NOT NULL, `bolusIOB` REAL NOT NULL, `wasBolusIOBUsed` INTEGER NOT NULL, `basalIOB` REAL NOT NULL, `wasBasalIOBUsed` INTEGER NOT NULL, `glucoseValue` REAL NOT NULL, `wasGlucoseUsed` INTEGER NOT NULL, `glucoseDifference` REAL NOT NULL, `glucoseInsulin` REAL NOT NULL, `glucoseTrend` REAL NOT NULL, `wasTrendUsed` INTEGER NOT NULL, `trendInsulin` REAL NOT NULL, `cob` REAL NOT NULL, `wasCOBUsed` INTEGER NOT NULL, `cobInsulin` REAL NOT NULL, `carbs` REAL NOT NULL, `wereCarbsUsed` INTEGER NOT NULL, `carbsInsulin` REAL NOT NULL, `otherCorrection` REAL NOT NULL, `wasSuperbolusUsed` INTEGER NOT NULL, `superbolusInsulin` REAL NOT NULL, `wasTempTargetUsed` INTEGER NOT NULL, `totalInsulin` REAL NOT NULL, `percentageCorrection` INTEGER NOT NULL, `profileName` TEXT NOT NULL, `note` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `bolusCalculatorResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "targetBGLow", + "columnName": "targetBGLow", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "targetBGHigh", + "columnName": "targetBGHigh", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isf", + "columnName": "isf", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "ic", + "columnName": "ic", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "bolusIOB", + "columnName": "bolusIOB", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasBolusIOBUsed", + "columnName": "wasBolusIOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalIOB", + "columnName": "basalIOB", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasBasalIOBUsed", + "columnName": "wasBasalIOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "glucoseValue", + "columnName": "glucoseValue", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasGlucoseUsed", + "columnName": "wasGlucoseUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "glucoseDifference", + "columnName": "glucoseDifference", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "glucoseInsulin", + "columnName": "glucoseInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "glucoseTrend", + "columnName": "glucoseTrend", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasTrendUsed", + "columnName": "wasTrendUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trendInsulin", + "columnName": "trendInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "cob", + "columnName": "cob", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasCOBUsed", + "columnName": "wasCOBUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cobInsulin", + "columnName": "cobInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wereCarbsUsed", + "columnName": "wereCarbsUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "carbsInsulin", + "columnName": "carbsInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "otherCorrection", + "columnName": "otherCorrection", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasSuperbolusUsed", + "columnName": "wasSuperbolusUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "superbolusInsulin", + "columnName": "superbolusInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "wasTempTargetUsed", + "columnName": "wasTempTargetUsed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalInsulin", + "columnName": "totalInsulin", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "percentageCorrection", + "columnName": "percentageCorrection", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_bolusCalculatorResults_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_bolusCalculatorResults_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_bolusCalculatorResults_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_bolusCalculatorResults_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_bolusCalculatorResults_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "bolusCalculatorResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "carbs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `amount` REAL NOT NULL, `notes` TEXT, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `carbs`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_carbs_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_carbs_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_carbs_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_carbs_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_carbs_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_carbs_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "carbs", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "effectiveProfileSwitches", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalBlocks` TEXT NOT NULL, `isfBlocks` TEXT NOT NULL, `icBlocks` TEXT NOT NULL, `targetBlocks` TEXT NOT NULL, `glucoseUnit` TEXT NOT NULL, `originalProfileName` TEXT NOT NULL, `originalCustomizedName` TEXT NOT NULL, `originalTimeshift` INTEGER NOT NULL, `originalPercentage` INTEGER NOT NULL, `originalDuration` INTEGER NOT NULL, `originalEnd` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT NOT NULL, `insulinEndTime` INTEGER NOT NULL, `peak` INTEGER NOT NULL, FOREIGN KEY(`referenceId`) REFERENCES `effectiveProfileSwitches`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalBlocks", + "columnName": "basalBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isfBlocks", + "columnName": "isfBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icBlocks", + "columnName": "icBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetBlocks", + "columnName": "targetBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalProfileName", + "columnName": "originalProfileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalCustomizedName", + "columnName": "originalCustomizedName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "originalTimeshift", + "columnName": "originalTimeshift", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalPercentage", + "columnName": "originalPercentage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalDuration", + "columnName": "originalDuration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalEnd", + "columnName": "originalEnd", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_effectiveProfileSwitches_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_effectiveProfileSwitches_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_effectiveProfileSwitches_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_effectiveProfileSwitches_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_effectiveProfileSwitches_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "effectiveProfileSwitches", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "extendedBoluses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `amount` REAL NOT NULL, `isEmulatingTempBasal` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `extendedBoluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isEmulatingTempBasal", + "columnName": "isEmulatingTempBasal", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_extendedBoluses_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_extendedBoluses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_extendedBoluses_endId", + "unique": false, + "columnNames": [ + "endId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_endId` ON `${TABLE_NAME}` (`endId`)" + }, + { + "name": "index_extendedBoluses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_extendedBoluses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_extendedBoluses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_extendedBoluses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_extendedBoluses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_extendedBoluses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "extendedBoluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "glucoseValues", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `raw` REAL, `value` REAL NOT NULL, `trendArrow` TEXT NOT NULL, `noise` REAL, `sourceSensor` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `glucoseValues`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "raw", + "columnName": "raw", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "trendArrow", + "columnName": "trendArrow", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "noise", + "columnName": "noise", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "sourceSensor", + "columnName": "sourceSensor", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_glucoseValues_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_glucoseValues_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_glucoseValues_sourceSensor", + "unique": false, + "columnNames": [ + "sourceSensor" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_sourceSensor` ON `${TABLE_NAME}` (`sourceSensor`)" + }, + { + "name": "index_glucoseValues_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_glucoseValues_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_glucoseValues_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "glucoseValues", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "profileSwitches", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalBlocks` TEXT NOT NULL, `isfBlocks` TEXT NOT NULL, `icBlocks` TEXT NOT NULL, `targetBlocks` TEXT NOT NULL, `glucoseUnit` TEXT NOT NULL, `profileName` TEXT NOT NULL, `timeshift` INTEGER NOT NULL, `percentage` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, `insulinLabel` TEXT NOT NULL, `insulinEndTime` INTEGER NOT NULL, `peak` INTEGER NOT NULL, FOREIGN KEY(`referenceId`) REFERENCES `profileSwitches`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalBlocks", + "columnName": "basalBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isfBlocks", + "columnName": "isfBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icBlocks", + "columnName": "icBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetBlocks", + "columnName": "targetBlocks", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "profileName", + "columnName": "profileName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeshift", + "columnName": "timeshift", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "percentage", + "columnName": "percentage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "insulinConfiguration.insulinLabel", + "columnName": "insulinLabel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.insulinEndTime", + "columnName": "insulinEndTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insulinConfiguration.peak", + "columnName": "peak", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_profileSwitches_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_profileSwitches_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + }, + { + "name": "index_profileSwitches_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_profileSwitches_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_profileSwitches_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_profileSwitches_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + } + ], + "foreignKeys": [ + { + "table": "profileSwitches", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "temporaryBasals", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `type` TEXT NOT NULL, `isAbsolute` INTEGER NOT NULL, `rate` REAL NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `temporaryBasals`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAbsolute", + "columnName": "isAbsolute", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_temporaryBasals_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_temporaryBasals_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_temporaryBasals_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_temporaryBasals_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_temporaryBasals_endId", + "unique": false, + "columnNames": [ + "endId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_endId` ON `${TABLE_NAME}` (`endId`)" + }, + { + "name": "index_temporaryBasals_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_temporaryBasals_temporaryId", + "unique": false, + "columnNames": [ + "temporaryId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_temporaryId` ON `${TABLE_NAME}` (`temporaryId`)" + }, + { + "name": "index_temporaryBasals_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_temporaryBasals_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryBasals_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "temporaryBasals", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "temporaryTargets", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `reason` TEXT NOT NULL, `highTarget` REAL NOT NULL, `lowTarget` REAL NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `temporaryTargets`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "highTarget", + "columnName": "highTarget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "lowTarget", + "columnName": "lowTarget", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_temporaryTargets_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_temporaryTargets_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_temporaryTargets_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_temporaryTargets_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_temporaryTargets_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_temporaryTargets_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "temporaryTargets", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "therapyEvents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `type` TEXT NOT NULL, `note` TEXT, `enteredBy` TEXT, `glucose` REAL, `glucoseType` TEXT, `glucoseUnit` TEXT NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `therapyEvents`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "enteredBy", + "columnName": "enteredBy", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "glucose", + "columnName": "glucose", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "glucoseType", + "columnName": "glucoseType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "glucoseUnit", + "columnName": "glucoseUnit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_therapyEvents_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_therapyEvents_type", + "unique": false, + "columnNames": [ + "type" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_type` ON `${TABLE_NAME}` (`type`)" + }, + { + "name": "index_therapyEvents_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_therapyEvents_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_therapyEvents_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_therapyEvents_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_therapyEvents_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "therapyEvents", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "totalDailyDoses", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `basalAmount` REAL NOT NULL, `bolusAmount` REAL NOT NULL, `totalAmount` REAL NOT NULL, `carbs` REAL NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `totalDailyDoses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "basalAmount", + "columnName": "basalAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "bolusAmount", + "columnName": "bolusAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "totalAmount", + "columnName": "totalAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_totalDailyDoses_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_totalDailyDoses_pumpId", + "unique": false, + "columnNames": [ + "pumpId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpId` ON `${TABLE_NAME}` (`pumpId`)" + }, + { + "name": "index_totalDailyDoses_pumpType", + "unique": false, + "columnNames": [ + "pumpType" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpType` ON `${TABLE_NAME}` (`pumpType`)" + }, + { + "name": "index_totalDailyDoses_pumpSerial", + "unique": false, + "columnNames": [ + "pumpSerial" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_pumpSerial` ON `${TABLE_NAME}` (`pumpSerial`)" + }, + { + "name": "index_totalDailyDoses_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_totalDailyDoses_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_totalDailyDoses_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_totalDailyDoses_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "totalDailyDoses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "apsResultLinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `apsResultId` INTEGER NOT NULL, `smbId` INTEGER, `tbrId` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`apsResultId`) REFERENCES `apsResults`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`smbId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`tbrId`) REFERENCES `temporaryBasals`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`referenceId`) REFERENCES `apsResultLinks`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "apsResultId", + "columnName": "apsResultId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "smbId", + "columnName": "smbId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "tbrId", + "columnName": "tbrId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_apsResultLinks_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_apsResultLinks_apsResultId", + "unique": false, + "columnNames": [ + "apsResultId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_apsResultId` ON `${TABLE_NAME}` (`apsResultId`)" + }, + { + "name": "index_apsResultLinks_smbId", + "unique": false, + "columnNames": [ + "smbId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_smbId` ON `${TABLE_NAME}` (`smbId`)" + }, + { + "name": "index_apsResultLinks_tbrId", + "unique": false, + "columnNames": [ + "tbrId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_apsResultLinks_tbrId` ON `${TABLE_NAME}` (`tbrId`)" + } + ], + "foreignKeys": [ + { + "table": "apsResults", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "apsResultId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "smbId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "temporaryBasals", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "tbrId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "apsResultLinks", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "multiwaveBolusLinks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `bolusId` INTEGER NOT NULL, `extendedBolusId` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`bolusId`) REFERENCES `boluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`extendedBolusId`) REFERENCES `extendedBoluses`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`referenceId`) REFERENCES `multiwaveBolusLinks`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bolusId", + "columnName": "bolusId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extendedBolusId", + "columnName": "extendedBolusId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_multiwaveBolusLinks_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_multiwaveBolusLinks_bolusId", + "unique": false, + "columnNames": [ + "bolusId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_bolusId` ON `${TABLE_NAME}` (`bolusId`)" + }, + { + "name": "index_multiwaveBolusLinks_extendedBolusId", + "unique": false, + "columnNames": [ + "extendedBolusId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_multiwaveBolusLinks_extendedBolusId` ON `${TABLE_NAME}` (`extendedBolusId`)" + } + ], + "foreignKeys": [ + { + "table": "boluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "bolusId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "extendedBoluses", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "extendedBolusId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "multiwaveBolusLinks", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "preferenceChanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `key` TEXT NOT NULL, `value` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "versionChanges", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `versionCode` INTEGER NOT NULL, `versionName` TEXT NOT NULL, `gitRemote` TEXT, `commitHash` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionCode", + "columnName": "versionCode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "versionName", + "columnName": "versionName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gitRemote", + "columnName": "gitRemote", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "commitHash", + "columnName": "commitHash", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "userEntry", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `action` TEXT NOT NULL, `source` TEXT NOT NULL, `note` TEXT NOT NULL, `values` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "values", + "columnName": "values", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_userEntry_source", + "unique": false, + "columnNames": [ + "source" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_userEntry_source` ON `${TABLE_NAME}` (`source`)" + }, + { + "name": "index_userEntry_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_userEntry_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "foods", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `name` TEXT NOT NULL, `category` TEXT, `subCategory` TEXT, `portion` REAL NOT NULL, `carbs` INTEGER NOT NULL, `fat` INTEGER, `protein` INTEGER, `energy` INTEGER, `unit` TEXT NOT NULL, `gi` INTEGER, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `foods`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subCategory", + "columnName": "subCategory", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "portion", + "columnName": "portion", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "carbs", + "columnName": "carbs", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fat", + "columnName": "fat", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "protein", + "columnName": "protein", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "energy", + "columnName": "energy", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "gi", + "columnName": "gi", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_foods_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_foods_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_foods_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_foods_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_foods_isValid` ON `${TABLE_NAME}` (`isValid`)" + } + ], + "foreignKeys": [ + { + "table": "foods", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "deviceStatus", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `device` TEXT, `pump` TEXT, `enacted` TEXT, `suggested` TEXT, `iob` TEXT, `uploaderBattery` INTEGER NOT NULL, `configuration` TEXT, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "device", + "columnName": "device", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "pump", + "columnName": "pump", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "enacted", + "columnName": "enacted", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "suggested", + "columnName": "suggested", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "iob", + "columnName": "iob", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "uploaderBattery", + "columnName": "uploaderBattery", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "configuration", + "columnName": "configuration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_deviceStatus_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_deviceStatus_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_deviceStatus_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_deviceStatus_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "offlineEvents", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `version` INTEGER NOT NULL, `dateCreated` INTEGER NOT NULL, `isValid` INTEGER NOT NULL, `referenceId` INTEGER, `timestamp` INTEGER NOT NULL, `utcOffset` INTEGER NOT NULL, `reason` TEXT NOT NULL, `duration` INTEGER NOT NULL, `nightscoutSystemId` TEXT, `nightscoutId` TEXT, `pumpType` TEXT, `pumpSerial` TEXT, `temporaryId` INTEGER, `pumpId` INTEGER, `startId` INTEGER, `endId` INTEGER, FOREIGN KEY(`referenceId`) REFERENCES `offlineEvents`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateCreated", + "columnName": "dateCreated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isValid", + "columnName": "isValid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "utcOffset", + "columnName": "utcOffset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reason", + "columnName": "reason", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutSystemId", + "columnName": "nightscoutSystemId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.nightscoutId", + "columnName": "nightscoutId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpType", + "columnName": "pumpType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpSerial", + "columnName": "pumpSerial", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.temporaryId", + "columnName": "temporaryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.pumpId", + "columnName": "pumpId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.startId", + "columnName": "startId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "interfaceIDs_backing.endId", + "columnName": "endId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_offlineEvents_id", + "unique": false, + "columnNames": [ + "id" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_offlineEvents_isValid", + "unique": false, + "columnNames": [ + "isValid" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_isValid` ON `${TABLE_NAME}` (`isValid`)" + }, + { + "name": "index_offlineEvents_nightscoutId", + "unique": false, + "columnNames": [ + "nightscoutId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_nightscoutId` ON `${TABLE_NAME}` (`nightscoutId`)" + }, + { + "name": "index_offlineEvents_referenceId", + "unique": false, + "columnNames": [ + "referenceId" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_referenceId` ON `${TABLE_NAME}` (`referenceId`)" + }, + { + "name": "index_offlineEvents_timestamp", + "unique": false, + "columnNames": [ + "timestamp" + ], + "createSql": "CREATE INDEX IF NOT EXISTS `index_offlineEvents_timestamp` ON `${TABLE_NAME}` (`timestamp`)" + } + ], + "foreignKeys": [ + { + "table": "offlineEvents", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "referenceId" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '09121464fb795b3c37bb1c2c2c3ea481')" + ] + } +} \ No newline at end of file diff --git a/database/impl/src/androidTest/kotlin/app/aaps/database/impl/HeartRateDaoTest.kt b/database/impl/src/androidTest/kotlin/app/aaps/database/impl/HeartRateDaoTest.kt index 669a5dbe7f..11ef58980f 100644 --- a/database/impl/src/androidTest/kotlin/app/aaps/database/impl/HeartRateDaoTest.kt +++ b/database/impl/src/androidTest/kotlin/app/aaps/database/impl/HeartRateDaoTest.kt @@ -16,7 +16,7 @@ import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -internal class HeartRateDaoTest { +class HeartRateDaoTest { private val context = ApplicationProvider.getApplicationContext() private fun createDatabase() = @@ -86,9 +86,9 @@ internal class HeartRateDaoTest { dao.insertNewEntry(hr1) dao.insertNewEntry(hr2) - assertEquals(listOf(hr1, hr2), dao.getFromTime(timestamp)) - assertEquals(listOf(hr2), dao.getFromTime(timestamp + 1)) - assertTrue(dao.getFromTime(timestamp + 2).isEmpty()) + assertEquals(listOf(hr1, hr2), dao.getFromTime(timestamp).blockingGet()) + assertEquals(listOf(hr2), dao.getFromTime(timestamp + 1).blockingGet()) + assertTrue(dao.getFromTime(timestamp + 2).blockingGet().isEmpty()) } } diff --git a/database/impl/src/main/java/app/aaps/database/impl/AppRepository.kt b/database/impl/src/main/java/app/aaps/database/impl/AppRepository.kt index c5cb3fbcd4..7cccd799e3 100644 --- a/database/impl/src/main/java/app/aaps/database/impl/AppRepository.kt +++ b/database/impl/src/main/java/app/aaps/database/impl/AppRepository.kt @@ -86,7 +86,7 @@ import kotlin.math.roundToInt removed.add(Pair("ExtendedBolus", database.extendedBolusDao.deleteOlderThan(than))) removed.add(Pair("Bolus", database.bolusDao.deleteOlderThan(than))) removed.add(Pair("MultiWaveBolus", database.multiwaveBolusLinkDao.deleteOlderThan(than))) - // keep TDD removed.add(Pair("TotalDailyDose", database.totalDailyDoseDao.deleteOlderThan(than))) + removed.add(Pair("TotalDailyDose", database.totalDailyDoseDao.deleteOlderThan(than))) removed.add(Pair("Carbs", database.carbsDao.deleteOlderThan(than))) removed.add(Pair("TemporaryTarget", database.temporaryTargetDao.deleteOlderThan(than))) removed.add(Pair("ApsResultLink", database.apsResultLinkDao.deleteOlderThan(than))) @@ -111,7 +111,7 @@ import kotlin.math.roundToInt removed.add(Pair("CHANGES Bolus", database.bolusDao.deleteTrackedChanges())) removed.add(Pair("CHANGES ExtendedBolus", database.extendedBolusDao.deleteTrackedChanges())) removed.add(Pair("CHANGES MultiWaveBolus", database.multiwaveBolusLinkDao.deleteTrackedChanges())) - // keep TDD removed.add(Pair("CHANGES TotalDailyDose", database.totalDailyDoseDao.deleteTrackedChanges())) + removed.add(Pair("CHANGES TotalDailyDose", database.totalDailyDoseDao.deleteTrackedChanges())) removed.add(Pair("CHANGES Carbs", database.carbsDao.deleteTrackedChanges())) removed.add(Pair("CHANGES TemporaryTarget", database.temporaryTargetDao.deleteTrackedChanges())) removed.add(Pair("CHANGES ApsResultLink", database.apsResultLinkDao.deleteTrackedChanges())) diff --git a/database/impl/src/main/java/app/aaps/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt b/database/impl/src/main/java/app/aaps/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt index c9c56975b6..14b58ea1d5 100644 --- a/database/impl/src/main/java/app/aaps/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt +++ b/database/impl/src/main/java/app/aaps/database/impl/transactions/InsertOrUpdateHeartRateTransaction.kt @@ -17,5 +17,16 @@ class InsertOrUpdateHeartRateTransaction(private val heartRate: HeartRate) : } } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as InsertOrUpdateHeartRateTransaction + return heartRate == other.heartRate + } + + override fun hashCode(): Int { + return heartRate.hashCode() + } + data class TransactionResult(val inserted: List, val updated: List) } diff --git a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt index 46b4ca5f74..35c42443a3 100644 --- a/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt +++ b/implementation/src/main/kotlin/app/aaps/implementation/userEntry/UserEntryPresentationHelperImpl.kt @@ -108,6 +108,7 @@ class UserEntryPresentationHelperImpl @Inject constructor( Sources.ConfigBuilder -> app.aaps.core.ui.R.drawable.ic_cogs Sources.Overview -> app.aaps.core.ui.R.drawable.ic_home Sources.Aaps -> R.drawable.ic_aaps + Sources.GarminDevice -> app.aaps.core.ui.R.drawable.ic_generic_icon Sources.Unknown -> app.aaps.core.ui.R.drawable.ic_generic_icon } diff --git a/plugins/aps/src/main/res/values-nb-rNO/strings.xml b/plugins/aps/src/main/res/values-nb-rNO/strings.xml index c66aa421a2..e508c95944 100644 --- a/plugins/aps/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/aps/src/main/res/values-nb-rNO/strings.xml @@ -41,8 +41,8 @@ Bruk Autosens-funksjon Max E/t en midl. basal kan settes til Denne verdien kalles max basal i OpenAPS - Maksimum basal IOB som OpenAPS kan levere [U] - Denne verdien kalles Max IOB i OpenAPS.\nDet er maks insulinmengde i [U] som APS kan levere i en dose. + Maksimum basal IOB som OpenAPS kan levere [E] + Denne verdien kalles Max IOB i OpenAPS.\nDet er maks insulinmengde i [E] som APS kan levere i en dose. Standard verdi: sann\nGir autosens tillatelse til å justere BS-mål, i tillegg til ISF og basaler. Autosens justerer også BS målverdier Standardverdi er: 3.0 (AMA) eller 8.0 (SMB). Dette er grunninnstillingen for KH-opptak per 5 minutt. Den påvirker hvor raskt COB skal reduseres, og benyttes i beregning av fremtidig BS-kurve når BS enten synker eller øker mer enn forventet. Standardverdi er 3mg/dl/5 min. @@ -54,8 +54,8 @@ Nyttig når data fra ufiltrerte kilder som xDrip+ registrerer mye støy. Multiplikator for maks daglig basal Multiplikator for gjeldende basal - Maks total IOB OpenAPS ikke kan overstige [U] - Denne verdien kalles Maks IOB av OpenAPS\nOpenAPS vil ikke gi mere insulin hvis mengden insulin ombord (IOB) overstiger denne verdien + Maks total IOB OpenAPS ikke kan overstige [E] + Denne verdien kalles Maks IOB av OpenAPS\nAAPS vil ikke gi mere insulin hvis mengden insulin ombord (IOB) overstiger denne verdien Aktiver UAM Aktiver SMB Bruk Supermikrobolus i stedet for midl. basal for raskere resultat @@ -65,7 +65,7 @@ Aktiver SMB etter karbohydrater Aktiver SMB i 6t etter karbohydratinntak, selv med 0 COB. Bare mulig med en bra filtrert BS kilde som f. eks. Dexcom G5/G6 Aktiver SMB med COB - Aktiver SMB når COB er aktiv. + Aktiver SMB når COB (karbohydrater ombord) er aktiv. Aktiver SMB med midl. målverdi Aktiver SMB når midl. målverdi er aktivert (spise snart, trening) Aktiver SMB ved høy midl. målverdi diff --git a/plugins/automation/src/main/kotlin/app/aaps/plugins/automation/AutomationPlugin.kt b/plugins/automation/src/main/kotlin/app/aaps/plugins/automation/AutomationPlugin.kt index 89e821b52b..64a3a11bd5 100644 --- a/plugins/automation/src/main/kotlin/app/aaps/plugins/automation/AutomationPlugin.kt +++ b/plugins/automation/src/main/kotlin/app/aaps/plugins/automation/AutomationPlugin.kt @@ -129,7 +129,7 @@ class AutomationPlugin @Inject constructor( init { refreshLoop = Runnable { processActions() - handler.postDelayed(refreshLoop, T.mins(1).msecs()) + handler.postDelayed(refreshLoop, T.secs(150).msecs()) } } diff --git a/plugins/automation/src/main/res/values-nb-rNO/strings.xml b/plugins/automation/src/main/res/values-nb-rNO/strings.xml index 0b67c24fef..6a838824ae 100644 --- a/plugins/automation/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/automation/src/main/res/values-nb-rNO/strings.xml @@ -87,7 +87,7 @@ COB %1$s %2$.0f Puls HR %1$s %2$.0f - IOB [U]: + IOB [E]: Dist [m]: Gjentakende tidspunkt Hver diff --git a/plugins/configuration/src/main/assets/SteamPunk mgdl.zip b/plugins/configuration/src/main/assets/SteamPunk mgdl.zip index a1d1ae1395..f78a49f240 100644 Binary files a/plugins/configuration/src/main/assets/SteamPunk mgdl.zip and b/plugins/configuration/src/main/assets/SteamPunk mgdl.zip differ diff --git a/plugins/configuration/src/main/assets/SteamPunk mmol.zip b/plugins/configuration/src/main/assets/SteamPunk mmol.zip index 1076b2afcf..e15fb0fc2b 100644 Binary files a/plugins/configuration/src/main/assets/SteamPunk mmol.zip and b/plugins/configuration/src/main/assets/SteamPunk mmol.zip differ diff --git a/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/PrefFileListProviderImpl.kt b/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/PrefFileListProviderImpl.kt index fe466ef4e9..33f1162eca 100644 --- a/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/PrefFileListProviderImpl.kt +++ b/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/PrefFileListProviderImpl.kt @@ -12,6 +12,7 @@ import app.aaps.core.interfaces.maintenance.PrefsMetadataKey import app.aaps.core.interfaces.resources.ResourceHelper import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.weardata.CwfData +import app.aaps.core.interfaces.rx.weardata.CwfFile import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.rx.weardata.ZipWatchfaceFormat import app.aaps.core.interfaces.sharedPreferences.SP @@ -97,11 +98,11 @@ class PrefFileListProviderImpl @Inject constructor( return prefFiles } - override fun listCustomWatchfaceFiles(): MutableList { - val customWatchfaceFiles = mutableListOf() - val customAwtchfaceAuthorization = sp.getBoolean(app.aaps.core.utils.R.string.key_wear_custom_watchface_autorization, false) + override fun listCustomWatchfaceFiles(): MutableList { + val customWatchfaceFiles = mutableListOf() + val customWatchfaceAuthorization = sp.getBoolean(app.aaps.core.utils.R.string.key_wear_custom_watchface_autorization, false) exportsPath.walk().filter { it.isFile && it.name.endsWith(ZipWatchfaceFormat.CWF_EXTENTION) }.forEach { file -> - ZipWatchfaceFormat.loadCustomWatchface(ZipInputStream(file.inputStream()), file.name, customAwtchfaceAuthorization)?.also { customWatchface -> + ZipWatchfaceFormat.loadCustomWatchface(file.readBytes(), file.name, customWatchfaceAuthorization)?.also { customWatchface -> customWatchfaceFiles.add(customWatchface) } } @@ -110,12 +111,11 @@ class PrefFileListProviderImpl @Inject constructor( val assetFiles = context.assets.list("") ?: arrayOf() for (assetFileName in assetFiles) { if (assetFileName.endsWith(ZipWatchfaceFormat.CWF_EXTENTION)) { - val assetInputStream = context.assets.open(assetFileName) - ZipWatchfaceFormat.loadCustomWatchface(ZipInputStream(assetInputStream), assetFileName, customAwtchfaceAuthorization)?.also { customWatchface -> + val assetByteArray = context.assets.open(assetFileName).readBytes() + ZipWatchfaceFormat.loadCustomWatchface(assetByteArray, assetFileName, customWatchfaceAuthorization)?.also { customWatchface -> customWatchfaceFiles.add(customWatchface) - rxBus.send(EventData.ActionGetCustomWatchface(EventData.ActionSetCustomWatchface(customWatchface), exportFile = true, withDate = false)) + rxBus.send(EventData.ActionGetCustomWatchface(EventData.ActionSetCustomWatchface(customWatchface.cwfData), exportFile = true, withDate = false)) } - assetInputStream.close() } } } catch (e: Exception) { diff --git a/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt b/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt index f42849b993..3858a4135d 100644 --- a/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt +++ b/plugins/configuration/src/main/kotlin/app/aaps/plugins/configuration/maintenance/activities/CustomWatchfaceImportListActivity.kt @@ -15,6 +15,7 @@ import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventMobileDataToWear import app.aaps.core.interfaces.rx.weardata.CUSTOM_VERSION import app.aaps.core.interfaces.rx.weardata.CwfData +import app.aaps.core.interfaces.rx.weardata.CwfFile import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_AUTHOR import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_AUTHOR_VERSION import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_CREATED_AT @@ -22,6 +23,7 @@ import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_FILENAME import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_NAME import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey.CWF_VERSION import app.aaps.core.interfaces.rx.weardata.CwfMetadataMap +import app.aaps.core.interfaces.rx.weardata.CwfResDataMap import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.rx.weardata.ResFileMap import app.aaps.core.interfaces.rx.weardata.ZipWatchfaceFormat @@ -56,10 +58,10 @@ class CustomWatchfaceImportListActivity : TranslatedDaggerAppCompatActivity() { supportActionBar?.setDisplayShowTitleEnabled(true) binding.recyclerview.layoutManager = LinearLayoutManager(this) - binding.recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listCustomWatchfaceFiles().sortedBy { it.metadata[CWF_NAME] }) + binding.recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listCustomWatchfaceFiles().sortedBy { it.cwfData.metadata[CWF_NAME] }) } - inner class RecyclerViewAdapter internal constructor(private var customWatchfaceFileList: List) : RecyclerView.Adapter() { + inner class RecyclerViewAdapter internal constructor(private var customWatchfaceFileList: List) : RecyclerView.Adapter() { inner class CwfFileViewHolder(val customWatchfaceImportListItemBinding: CustomWatchfaceImportListItemBinding) : RecyclerView.ViewHolder(customWatchfaceImportListItemBinding.root) { @@ -67,11 +69,14 @@ class CustomWatchfaceImportListActivity : TranslatedDaggerAppCompatActivity() { with(customWatchfaceImportListItemBinding) { root.isClickable = true customWatchfaceImportListItemBinding.root.setOnClickListener { - val customWatchfaceFile = filelistName.tag as CwfData - val customWF = EventData.ActionSetCustomWatchface(customWatchfaceFile) + val customWatchfaceFile = filelistName.tag as CwfFile + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_watchface_name, customWatchfaceFile.cwfData.metadata[CWF_NAME] ?:"") + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_author_version, customWatchfaceFile.cwfData.metadata[CWF_AUTHOR_VERSION] ?:"") + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_filename, customWatchfaceFile.cwfData.metadata[CWF_FILENAME] ?:"") + val i = Intent() setResult(FragmentActivity.RESULT_OK, i) - rxBus.send(EventMobileDataToWear(customWF)) + rxBus.send(EventMobileDataToWear(customWatchfaceFile.zipByteArray)) finish() } } @@ -89,8 +94,8 @@ class CustomWatchfaceImportListActivity : TranslatedDaggerAppCompatActivity() { override fun onBindViewHolder(holder: CwfFileViewHolder, position: Int) { val customWatchfaceFile = customWatchfaceFileList[position] - val metadata = customWatchfaceFile.metadata - val drawable = customWatchfaceFile.resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.toDrawable(resources) + val metadata = customWatchfaceFile.cwfData.metadata + val drawable = customWatchfaceFile.cwfData.resDatas[ResFileMap.CUSTOM_WATCHFACE.fileName]?.toDrawable(resources) with(holder.customWatchfaceImportListItemBinding) { val fileName = metadata[CWF_FILENAME]?.let { "$it${ZipWatchfaceFormat.CWF_EXTENTION}" } ?: "" filelistName.text = rh.gs(app.aaps.core.interfaces.R.string.metadata_wear_import_filename, fileName) diff --git a/plugins/configuration/src/main/res/values-nb-rNO/strings.xml b/plugins/configuration/src/main/res/values-nb-rNO/strings.xml index 3a00df1b2a..2b0b279c4c 100644 --- a/plugins/configuration/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/configuration/src/main/res/values-nb-rNO/strings.xml @@ -44,12 +44,12 @@ Merk: Du kan fortsette oppsettet når pumpen er satt opp.\n Start første læringsmål RileyLink status: - Les status + Lesestatus Datavalg Innlesing av fabrikkinnstillinger Tillat automatisk rapportering av appkrasj og bruksdata til utviklerne via fabrioc.io-tjenesten. Denne e-postadressen vedlegges krasjrapporter slik at vi kan kontakte deg i akutte tilfeller. Det er valgfritt. - Identifikasjon (e-post, Facebook eller Discord nick osv.) + Identifikasjon (e-post, Facebook eller Discord-nick osv.) Forespørsel APS modus Foretrukket APS modus @@ -117,12 +117,12 @@ Databaseopprydding Vil du virkelig nullstille databasene? Vedlikeholdsinnstillinger - E-post mottaker + Mottaker av e-post Antall logger du vil sende Send logger via e-post Slett logger - Nightscout versjon: - Engineering Mode aktivert + Nightscout-versjon: + Engineering mode aktivert Loggfiler Logginnstillinger Annet diff --git a/plugins/constraints/src/main/res/values-nb-rNO/exam.xml b/plugins/constraints/src/main/res/values-nb-rNO/exam.xml index 8da0332af8..ee21a0a22c 100644 --- a/plugins/constraints/src/main/res/values-nb-rNO/exam.xml +++ b/plugins/constraints/src/main/res/values-nb-rNO/exam.xml @@ -167,13 +167,13 @@ For å registrere karbohydrater som brukes til å korrigere lavt blodsukker. https://wiki.aaps.app/en/latest/Usage/Extended-Carbs.html Fjernovervåking - Hvordan kan du overvåke AndroidAPS (for eksempel for ditt barn) på eksternt? - AAPSClient app, Nightscout app og Nightscout websiden gjør det mulig for deg å følge AAPS eksternt. + Hvordan kan du eksternt overvåke AndroidAPS (for eksempel for ditt barn)? + Appene AAPSClient og xDrip+ samt Nightscout-websiden gjør det mulig for deg å følge AAPS eksternt. Andre apper (f.eks. Dexcom follow, xDrip som kjører i følger modus) lar deg følge noen parametere (f.eks. blodsukker/sensor verdier) på avstand, men bruker forskjellige beregningsmetoder og kan derfor vise andre IOB eller COB verdier. For å følge AAPS eksternt må begge enhetene ha Internett-tilgang (f.eks. via Wi-Fi eller mobildata). AAPSClient som brukes som ekstern følger app vil både overvåke og gi full kontroll over AAPS. https://wiki.aaps.app/en/latest/Children/Children.html - Insulin Sensitivitetsfaktor (ISF) + Insulinsensitivitetsfaktor (ISF) Økte ISF-verdiene vil føre til levering av mer insulin for å dekke opp en viss mengde karbohydrater. Redusering av ISF verdien vil føre til mer insulintilførsel for å korrigere et blodsukker som ligger over målverdien. Å øke eller senke ISF verdien har ingen effekt på insulintilførselen når blodsukkeret er lavere enn målverdien. @@ -211,7 +211,7 @@ Gjør et profilbytte til mer enn 100%. https://wiki.aaps.app/en/latest/Usage/Profiles.html#timeshift Endring av profil - Basalrater, ISF, KH ratio, etc., bør defineres i profiler. + Basaldoser, ISF, IK-faktor osv. bør defineres i profiler. Aktivering av endringer i Nightscout profilen din krever at din AndroidAPS telefon er koblet til Internett. Å redigere verdier i profilen din er tilstrekkelig for å aktivere profilendringen. Flere profiler kan defineres og velges for å håndtere endringer i omstendigheter (f.eks. hormonelle endringer, skift arbeid, hverdager/helgedager). diff --git a/plugins/constraints/src/main/res/values-nb-rNO/objectives.xml b/plugins/constraints/src/main/res/values-nb-rNO/objectives.xml index 291f461957..1ab33145aa 100644 --- a/plugins/constraints/src/main/res/values-nb-rNO/objectives.xml +++ b/plugins/constraints/src/main/res/values-nb-rNO/objectives.xml @@ -24,7 +24,7 @@ 1 uke vellykket looping på dagtid hvor alle måltider (KH) angis Hvis dine autosens resultater ikke ligger rundt 100% kan det tyde på at profilen din er feil. Aktiver ekstra funksjoner for bruk på dagtid, slik som SMB (Super Micro Bolus) - Du må lese wiki og øke din maxIOB for å få SMB til å fungere. Et godt utgangspunkt er maxIOB = gjennomsnittlig måltidsbolus + 3*max daglig basal + Du må lese wiki og øke din maxIOB for å få SMB til å fungere. Et godt utgangspunkt er å sette maxIOB til gjennomsnittlig måltidsbolus + 3x max daglig basaldose Bruk av SMB er en målsetting. Oref1 algoritmen ble designet for å hjelpe deg med dine bolusdoseringer. Det anbefales å ikke gi full bolusdose for måltider, men bare en del av den, og la AAPS styre resten om nødvendig. På denne måten får du større fleksibilitet med hensyn på feilberegnede KH. Visste du at du kan angi en prosentandel for boluskalkulatoren som resulterer i redusert bolusstørrelse? Aktivere ekstra funksjoner for bruk på dagtid, slik som Dynamisk Sensitivitet Sørg for at SMB fungerer som den skal. Aktiver DynamicISF-tillegget og finn riktig kalibrering for dine behov. Det anbefales å starte med en verdi lavere enn 100% for sikkerhets skyld. diff --git a/plugins/main/build.gradle b/plugins/main/build.gradle index 42103017e3..fa34723f9c 100644 --- a/plugins/main/build.gradle +++ b/plugins/main/build.gradle @@ -45,7 +45,4 @@ dependencies { // Food api "androidx.work:work-runtime-ktx:$work_version" - - // DataLayerListenerService - api "com.google.android.gms:play-services-wearable:$play_services_wearable_version" } \ No newline at end of file diff --git a/plugins/main/src/main/AndroidManifest.xml b/plugins/main/src/main/AndroidManifest.xml index 26e8677ac8..a1e7d61553 100644 --- a/plugins/main/src/main/AndroidManifest.xml +++ b/plugins/main/src/main/AndroidManifest.xml @@ -24,7 +24,7 @@ @@ -35,7 +35,7 @@ android:exported="false" /> diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/PluginsModule.kt b/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/PluginsModule.kt index 5f6a389b3b..b6f2e74936 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/PluginsModule.kt +++ b/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/PluginsModule.kt @@ -4,7 +4,6 @@ import app.aaps.core.interfaces.iob.IobCobCalculator import app.aaps.core.interfaces.smsCommunicator.SmsCommunicator import app.aaps.plugins.main.general.persistentNotification.DummyService import app.aaps.plugins.main.general.smsCommunicator.SmsCommunicatorPlugin -import app.aaps.plugins.main.general.wear.WearFragment import app.aaps.plugins.main.iob.iobCobCalculator.IobCobCalculatorPlugin import app.aaps.plugins.main.iob.iobCobCalculator.data.AutosensDataObject import dagger.Binds @@ -21,15 +20,13 @@ import dagger.android.ContributesAndroidInjector SkinsModule::class, SkinsUiModule::class, ActionsModule::class, - WearModule::class, - OverviewModule::class + OverviewModule::class, ] ) @Suppress("unused") abstract class PluginsModule { - @ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment @ContributesAndroidInjector abstract fun contributesDummyService(): DummyService @ContributesAndroidInjector abstract fun autosensDataObjectInjector(): AutosensDataObject diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/WearModule.kt b/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/WearModule.kt deleted file mode 100644 index 12b891b2a3..0000000000 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/di/WearModule.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.aaps.plugins.main.di - -import app.aaps.plugins.main.general.wear.activities.CwfInfosActivity -import app.aaps.plugins.main.general.wear.wearintegration.DataLayerListenerServiceMobile -import dagger.Module -import dagger.android.ContributesAndroidInjector - -@Module -@Suppress("unused") -abstract class WearModule { - - @ContributesAndroidInjector abstract fun contributesWatchUpdaterService(): DataLayerListenerServiceMobile - @ContributesAndroidInjector abstract fun contributesCustomWatchfaceInfosActivity(): CwfInfosActivity -} \ No newline at end of file diff --git a/plugins/main/src/main/res/values-bg-rBG/strings.xml b/plugins/main/src/main/res/values-bg-rBG/strings.xml index dbbd65c5da..fcc82b8878 100644 --- a/plugins/main/src/main/res/values-bg-rBG/strings.xml +++ b/plugins/main/src/main/res/values-bg-rBG/strings.xml @@ -285,72 +285,7 @@ употреба: Изпрати последните лог файлове на разработчиците. Непредвидена ситуация. - - WEAR - Наблюдавайте и контролирайте AndroidAPS, от вашия WearOS часовник. - (Няма активна връзка) - Статус на помпата - Статус на цикъл - Съветник:\nИнсулин: %1$.2fЕ\nВъгл: %2$dгр - Избраният съветник вече не е наличен, моля, опреснете - Съветник:%1$s\nИнсулин: %2$.2fЕ\nВъгл:%3$dгр - Временна цел непознат шаблон %1$s - Изключи текуща Временна цел? - Различни мерни единици се ползват на телефона и часовника! - Нулева цел - изключвам временна цел? - Мин КЗ е извън обхват! - Макс КЗ е извън обхват! - Временна цел \nМин: %1$s\nМакс: %2$s\nПрод: %3$s - Временна цел \nцел: %1$s\nПрод: %2$s - Временна цел \nЦел: %1$s\nЦел: %2$s\nПрод: %3$s - неуспешно - моля проверете телефона - Настройки на часовник - Контролиране от часовник - Задаване временни цели и въвеждане Лечения от часовник Android wear - Изчисления, включени в резултата на съветника: - Основни настройки - Уведомяване при SMB - Покажи SMB на часовника като стандартен болус. - Настройки на Watchface - Разрешаване на персонализиран Watchface - Разрешете заредения Watchface да променя и заключва определени настройки за екрана на часовника, за да съответстват на дизайна - Персонализиран Watchface: %1$s - Зареди Watchface - Информация за Watchface - Експорт на шаблона - Шаблон за watchface експортиран - Изпрати отново всички дани - Отвори настройките на часовника - Списък с опции заключен от Watchface - Списък с необходими настройки за Watchface - Списък с полета включени в Watchface Показва известие с резюме на това, което прави вашия APS СТАРИ ДАННИ - опитвам се да изтегля данни от помпата. - ОДД: Все още стари данни! Не може да се заредят от помпата. - гр. - ч - Не е настроен активен профил! - Профил:\n\nОтместване във времето: %1$d\nПроцент: %2$d%%\" - %1$.2fЕ %1$.0f%% - Не е зареден профил - Прилагане само в режим АПС! - Последният резултат не е наличен! - ЗАТВОРЕН ЦИКЪЛ - ОТВОРЕН ЦИКЪЛ - ЦИКЪЛ ИЗКЛЮЧЕН - АПС - Последно изпълнение - Последно зададено - Днес - тегло - Целите се прилагат само в режим АПС! - Няма хронология! - Е - Временна цел - до - НАЧАЛНИ СТОЙНОСТИ - цел - Скорост: %1$.2fЕ/ч (%2$.2f%%) \nПродължителност %3$d мин diff --git a/plugins/main/src/main/res/values-ca-rES/strings.xml b/plugins/main/src/main/res/values-ca-rES/strings.xml index c90209f159..bc949b8c70 100644 --- a/plugins/main/src/main/res/values-ca-rES/strings.xml +++ b/plugins/main/src/main/res/values-ca-rES/strings.xml @@ -250,18 +250,6 @@ Tema Enviar logs del dia als desenvolupadors. Situació inesperada. - - WEAR - no funciona - si us plau comproveu al mòbil - Configuració de Wear - Control des de rellotge - Marcar objectius temporals i introduir tractaments des del rellotge. - Càlculs inclosos al resultat de l\'assistent: - Configuració general - Avís d\'SMB - Mostrar SMB al rellotge com un bolus estàndard. - Reenviar totes les dades - Obrir Configuració a Wear Va mostrant avisos amb un petit resum del que el llaç està fent DADES ANTIGUES diff --git a/plugins/main/src/main/res/values-cs-rCZ/strings.xml b/plugins/main/src/main/res/values-cs-rCZ/strings.xml index f882884bc4..8befd04c68 100644 --- a/plugins/main/src/main/res/values-cs-rCZ/strings.xml +++ b/plugins/main/src/main/res/values-cs-rCZ/strings.xml @@ -285,72 +285,7 @@ použití: Odešlete dnešní soubory protokolů vývojářům spolu s tímto časem. Neočekávaná situace. - - WEAR - Zobrazování stavu a řízení AAPS z hodinek s WearOS. - (Žádné hodinky nejsou připojeny) - Stav pumpy - Stav smyčky - Kalkulátor: \nInzulín: %1$.2fU\nSacharidy: %2$dg - Vybraný rychlý bolus již není k dispozici, obnovte prosím dlaždici - Rychlý bolus: %1$s\nInzulín: %2$.2fU\nSacharidy: %3$dg - Dočasný cíl neznámá předvolba: %1$s - Zrušení běžícího dočasného cíle? - Různé jednotky používané na hodinkách a telefonu! - Nulový dočasný cíl - zrušení běžícího dočasného cíle? - Minimální glykémie mimo rozsah! - Maximální glykémie mimo rozsah! - Doč. cíl:\nMin: %1$s\nMax: %2$s\nTrvání: %3$s - Doč. cíl:\nCíl: %1$s\nTrvání: %2$s - Doč. cíl:\nDůvod: %1$s\nCíl: %2$s\nTrvání: %3$s - neúspěšně - zkontrolujte mobil - Nastavení hodinek - Řízení z hodinek Wear - Nastavování dočasných cílů a vkládání ošetření na hodinkách Wear. - Kalkulace použité ve výsledku wizardu: - Základní nastavení - Oznámení při SMB - Ukazovat SMB na hodinkách jako normální bolus. - Nastavení vlastního ciferníku - Autorizace vlastního ciferníku - Autorizujte načtený vlastní ciferník, aby se změnila a uzamkla některá nastavení hodinek tak, aby vyhovovala designu ciferníku - Vlastní ciferník: %1$s - Nahrát ciferník - Informace o ciferníku - Exportovat šablonu - Vlastní šablona ciferníku exportována - Znovu poslat všechna data - Otevřít nastavení na hodinkách Wear - Seznam preferencí uzamčený hodinkami - Seznam preferencí požadovaných ciferníkem - Seznam polí zahrnutých do ciferníku Zobrazení průběžného oznámení v Androidu s krátkým přehledem, co smyčka právě dělá ZASTARALÉ - pokus o načtení dat z pumpy. - CDD: Stále stará data! Nelze načíst z pumpy. - g - h - Není nastaven žádný aktivní profil! - Profil:\n\nPosunutí: %1$d\nProcento: %2$d%%\" - %1$.2fU %1$.0f%% - Není vybrán žádný profil - Použít pouze v APS módu! - Poslední výsledek není k dispozici! - UZAVŘENÁ SMYČKA - OTEVŘENÁ SMYČKA - SMYČKA ZAKÁZÁNA - APS - Poslední spuštění - Poslední provedení - Dnes - vážený - Cíle se použijí pouze v režimu APS! - Žádné údaje o historii! - U - Dočasný cíl - až do - VÝCHOZÍ ROZSAH - cíl - Rychlost: %1$.2fU/h (%2$.2f%%) \nTrvání %3$d min diff --git a/plugins/main/src/main/res/values-da-rDK/strings.xml b/plugins/main/src/main/res/values-da-rDK/strings.xml index f82470defa..04e4c10765 100644 --- a/plugins/main/src/main/res/values-da-rDK/strings.xml +++ b/plugins/main/src/main/res/values-da-rDK/strings.xml @@ -286,72 +286,7 @@ brug: Send dagens logfiler til udviklere sammen med denne tid. Uventet situation. - - UR - Overvåg og kontrollér AndroidAPS ved hjælp af dit WearOS-ur. - (Intet ur forbundet) - Pumpestatus - Loop status - Guide:\nInsulin: %1$.2fE\nKH: %2$dg - Valgt guide er ikke længere tilgængeligt. Opdater venligst din widget - Hurtigguide: %1$s\nInsulin: %2$.2fE\nKH: %3$dg - Midlertidigmål ukendt forudindstilling: %1$s - Annullér aktuelt midlertidig mål? - Forskellige enheder brugt på ur og telefon! - 0-mål - annuller midlertidigt mål? - Min-BS udenfor området! - Max-BS udenfor området! - Midlertidigt mål:\nMin: %1$s\nMax: %2$s\nVarighed: %3$s - Midlertigt mål:\nMål: %1$s\nVarighed: %2$s - Midlertigt mål:\nGrund: %1$s\nMål: %2$s\nVarighed: %3$s - mislykkedes - tjek venligst telefonen - Indstillinger for Wear - Kontrolleringer fra Ur - Sæt midlertidige mål og indtast behandlinger fra uret. - Beregninger inkluderet i guide resultatet: - Generelle indstillinger - Giv besked ved SMB - Vis SMB på uret som en standard bolus. - Brugerdefinerede Urskiveindstillinger - Brugerdefineret Urskivegodkendelse - Godkend indlæste brugerdefinerede urskive til at ændre og låse nogle overvågningsindstillinger der passer til urskivens design - Brugerdefineret Urskive: %1$s - Indlæs Urskive - Infos Urskiver - Eksporter skabelon - Brugerdefineret urskiveskabelon eksporteret - Send alle data igen - Åbn indstillinger på ur - Liste over præferencer låst af urskiven - Liste over præferencer påkrævet af urskiven - Liste over felter inkluderet i Urskiven Viser en løbende notifikation med en kort oversigt over, hvad dit loop gør GAMLE DATA - forsøger at hente data fra pumpen. - TDD: Stadig gammel data! Kan ikke indlæse fra pumpen. - g - t - Ingen aktiv profilskift! - Profil:\n\nTidsskift: %1$d\nProcent: %2$d%%\" - %1$.2fIE %1$.0f%% - Ingen profil indlæst - Gælder kun i APS-tilstand! - Sidste resultat ikke tilgængeligt! - LUKKET LOOP - ÅBEN LOOP - LOOP DEAKTIVERET - APS - Sidste brug - Sidste Aktivering - I dag - vægtet - Mål gælder kun i APS-tilstand! - Ingen historisk data! - IE - Midlertidigt mål - indtil - Standardmål - mål - Rate: %1$.2fIE/t (%2$.2f%%) \nVarighed %3$d min diff --git a/plugins/main/src/main/res/values-de-rDE/strings.xml b/plugins/main/src/main/res/values-de-rDE/strings.xml index a67be3ac51..2dd6068440 100644 --- a/plugins/main/src/main/res/values-de-rDE/strings.xml +++ b/plugins/main/src/main/res/values-de-rDE/strings.xml @@ -287,72 +287,7 @@ Sende die heutigen Logdateien unter Angabe dieser Uhrzeit an die Entwickler. Unerwartetes Verhalten. - - UHR - Überwache und steuere AndroidAPS mit Deiner WearOS-Smartwatch. - (keine Uhr verbunden) - Status der Pumpe - Loop Status - Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg - Ausgewählter Quickwizard nicht mehr verfügbar, bitte aktualisiere die Kachel - QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg - Temp-Target unbekannte Voreinstellung: %1$s - Ausführung des Temp-Targets abbrechen? - Verschiedene Einheiten werden auf Uhr und Telefon verwendet! - Zero-Temp-Target - abbrechen des laufenden Temp-Targets? - Min-BG ist außerhalb des Bereichs! - Max-BG ist außerhalb des Bereichs! - Temptarget:\nMin: %1$s\nMax: %2$s\nDauer: %3$s - Temptarget:\nTarget: %1$s\nDauer: %2$s - Temp-Target:\nGrund: %1$s\nTarget: %2$s\nDauer: %3$s - Nicht erfolgreich - bitte Telefon prüfen - Wear-Einstellungen - Steuerung durch die Uhr - Setze temporäre Ziele und Behandlungen mit der Uhr - Berechnungen, die im Assistenten berücksichtigt werden: - Allgemeine Einstellungen - Bei SMB benachrichtigen - Zeige SMB auf der Uhr wie einen normalen Bolus an. - Benutzerdefinierte Watchface Einstellungen - Benutzerdefinierte Watchface Autorisierung - Autorisiere geladene benutzerdefinierte Watchface zum Ändern und Sperren einiger Watchscreen-Einstellungen für das Design der Uhr - Benutzerdefiniertes Watchface: %1$s - Watchface laden - Infos Watchface - Vorlage exportieren - Benutzerdefinierte Watchface Vorlage exportiert - Alle Daten erneut senden - Öffne Einstellungen auf der Uhr - Liste der von der Watchface gesperrten Prefs - Liste der von der Watchface angeforderten Voreinstellungen - Liste der Felder, die in der Watchface enthalten sind Zeigt eine fortlaufende Benachrichtigung mit einer kurzen Übersicht darüber, was dein Loop derzeit tut VERALTETE DATEN - versuche Daten von der Pumpe abzurufen. - TDD: verwendet alte Daten! Auslesen der Pumpe nicht möglich. - g - h - Kein aktiver Profilwechsel! - Profil:\n\nZeitverschiebung: %1$\nProzent: %2$d%%\" - %1$.2fE %1$.0f%% - Kein Profil geladen - Nur im APS-Modus anwendbar! - Letztes Ergebnis ist nicht verfügbar! - CLOSED LOOP - OPEN LOOP - LOOP DEAKTIVIERT - APS - Letzte Ausführung - Letzte Abgabe - Heute - gewichtet - Ziele gelten nur im APS-Modus! - Keine Verlaufsdaten! - E - Temporäres Ziel - bis - STANDARD-BEREICH - Ziel - Rate: %1$.2fIE/h (%2$.2f%%) \nDauer %3$d min diff --git a/plugins/main/src/main/res/values-el-rGR/strings.xml b/plugins/main/src/main/res/values-el-rGR/strings.xml index b3db6ffc4d..04a9a52485 100644 --- a/plugins/main/src/main/res/values-el-rGR/strings.xml +++ b/plugins/main/src/main/res/values-el-rGR/strings.xml @@ -285,72 +285,7 @@ χρήσιμες συμβουλές: Στείλτε τα αρχεία καταγραφής της ημέρας στους προγραμματιστές μαζί με αυτή τη φορά. Απροσδόκητη κατάσταση. - - WEAR - Παρακολούθηση και χειρισμός του AAPS χρησιμοποιώντας το ρολόι WearOS. - (Χωρίς Σύνδεση Ρολογιού) - Κατάσταση Αντλίας - Κατάσταση κυκλώματος - Υπολογισμός. Οδηγός:\nΙνσουλίνη: %1$.2fU\nΥδατάνθρακες: %2$dg - Ο επιλεγμένος οδηγός δεν είναι πλέον διαθέσιμος, παρακαλώ ανανεώστε το πλακίδιο σας - Γρήγορος οδηγός: %1$s\nΙνσουλίνη: %2$.2fU\nΥδατάνθρακες: %3$dg - Άγνωστη προεπιλογή προσωρινού στόχου: %1$s - Ακύρωση εκτέλεσης προσωρινών στόχων; - Χρησιμοποιούνται διαφορετικές μονάδες στο ρολόι και το τηλέφωνο! - Μηδενικός-Προσωρινός-Στόχος - ακύρωση εκτέλεσης Πρισωρινών-Στόχων; - Ελάσιχτο-BG εκτός εύρους! - Μέγιστο-BG εκτός εύρους! - Προσωρινός στόχος:\nΕλάχιστο: %1$s\nΜέγιστο: %2$s\nΔιάρκεια: %3$s - Προσωρινός στόχος:\nΣτόχος: %1$s\nΔιάρκεια: %2$s - Προσωρινός στόχος:\nΑιτία: %1$s\nΣτόχος: %2$s\nΔιάρκεια: %3$s - αποτυχία - ελέγξτε τηλέφωνο - Ρυθμίσεις Wear - Έλεγχος από ρολόι - Ρυθμίστε Στόχους-Προσ Ρυθμού και βάλτε Θεραπείες από το ρολόι. - Υπολογισμοί που περιλαμβάνονται στο αποτέλεσμα του γρήγορου οδηγού: - Γενικές Ρυθμίσεις - Ειδοποίηση στο SMB - Εμφάνιση SMB στο ρολόι όπως ένα τυπικό bolus. - Προσαρμοσμένες Ρυθμίσεις Watchface - Προσαρμοσμένη Εξουσιοδότηση Watchface - Εξουσιοδοτήστε το φορτωμένο προσαρμοσμένο ρολόι για να αλλάξετε και να κλειδώσετε ορισμένες ρυθμίσεις οθόνης ρολογιού για να ταιριάζει στο σχεδιασμό watchface - Προσαρμοσμένο Watchface: %1$s - Φόρτωση Watchface - Πληροφορίες Watchface - Εξαγωγή Προτύπου - Εξήχθη προσαρμοσμένο πρότυπο watchface - Ξαναστείλτε όλα τα Δεδομένα - Ρυθμίσεις στο Wear - Λίστα προτιμήσεων που κλειδώθηκαν από το Watchface - Λίστα προτιμήσεων που απαιτούνται για το Watchface - Λίστα πεδίων που περιλαμβάνονται στο Watchface Εμφανίζει μία τρέχουσα ειδοποίηση με σύντομη επισκόπηση του τι κάνει το κύκλωμα τώρα Παλιά Δεδομένα - προσπάθεια λήψης δεδομένων από την αντλία. - TDD: Ακόμα είναι παλιά τα δεδομένα! Δεν μπορεί να φορτωθεί από την αντλία. - g - ω - Μη ενεργή αλλαγή προφίλ! - Προφίλ:\n\nΧρονική μετατόπιση: %1$d\nΠοσοστό: %2$d%%\" - %1$.2fU %1$.0f%% - Δεν φορτώθηκε προφίλ - Ισχύει μόνο σε λειτουργία APS! - Τελευταίο αποτέλεσμα μη διαθέσιμο! - ΚΛΕΙΣΤΟ ΚΥΚΛΩΜΑ - ΑΝΟΙΚΤΟ ΚΥΚΛΩΜΑ - ΚΥΚΛΩΜΑ ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΟ - APS - Τελευταία εκτέλεση - Τελευταία Ενέργεια - Σήμερα - σταθμισμένο - Οι στόχοι ισχύουν μόνο στη λειτουργία APS! - Δεν υπάρχουν ιστορικά δεδομένα! - U - Προσωρινός Στόχος - μέχρι - ΠΡΟΕΠΙΛΕΓΜΕΝΟ ΕΥΡΟΣ - στόχος - Τιμή: %1$.2fU/h (%2$.2f%%) \nΔιάρκεια %3$d λεπτά diff --git a/plugins/main/src/main/res/values-es-rES/strings.xml b/plugins/main/src/main/res/values-es-rES/strings.xml index 337a45a066..8e1855b502 100644 --- a/plugins/main/src/main/res/values-es-rES/strings.xml +++ b/plugins/main/src/main/res/values-es-rES/strings.xml @@ -286,72 +286,7 @@ Uso: Enviar los archivos de registro de hoy a los desarrolladores. Situación inesperada. - - RELOJ - Supervisar y controlar AAPS usando un reloj WearOS - (Ningún reloj conectado) - Estado de la bomba de insulina - Estado del bucle - Calc. Asistente:\nInsulina: %1$.2fU\nCarbohidratos: %2$dg - El asistente rápido seleccionado ya no está disponible, por favor actualice su tarjeta - Asistente Rápido: %1$s\nInsulina: %2$.2fU\nCarbohidratos: %3$dg - Objetivo Temporal preestablecido desconocido: %1$s - ¿Cancelar la ejecución del objetivo temporal? - ¡Diferentes unidades usadas en el reloj y en el teléfono! - Objetivo Temporal Zero - ¿Cancelar el Objetivo Temporal en ejecución? - ¡Glucosa mínima fuera de rango! - ¡Glucosa máxima fuera de rango! - Objetivo temporal:\nMin: %1$s\nMax: %2$s\nDuración: %3$s - Objetivo temporal:\nObjetivo: %1$s\nDuración: %2$s - ObjetivoTemporal:\nRazón: %1$s\nObjetivo: %2$s\nDuración: %3$s - sin efecto - por favor verificar en teléfono - Ajustes del reloj - Control desde el reloj - Establece objetivos temporales (OT) y añade tratamientos desde el reloj - Cálculos incluidos en el resultado del asistente: - Configuración general - Notificar los SMB - Mostrar los SMB en el reloj como un bolo estándar - Configuración personalizada de esferas - Autorización de esferas personalizadas - Autorizar a la esfera personalizada a cambiar y bloquear algunos ajustes de la pantalla del reloj, para que se adapten al diseño de la esfera - Esfera personalizada: %1$s - Cargar esfera - Información de esferas - Exportar plantilla - Plantilla de esfera personalizada exportada - Reenviar todos los datos - Abrir ajustes en el reloj - Lista de preferencias bloqueadas por la esfera - Lista de preferencias necesarias para la esfera - Lista de campos incluidos en la esfera Muestra una notificación en curso con un breve resumen de lo que está haciendo tu bucle DATOS CADUCADOS - tratando de obtener datos de la bomba. - TDD: ¡Siguen siendo datos antiguos! No se puede cargar desde la bomba - g - h - Sin cambio de perfil activo - Perfil:\n\nAjuste de tiempo: %1$d\nPorcentaje: %2$d%%\" - %1$.2fU %1$.0f%% - Ningún perfil cargado - Sólo se aplica en modo APS - ¡Último resultado no disponible! - BUCLE CERRADO - BUCLE ABIERTO - BUCLE DESACTIVADO - APS - Última ejecución - Último acto - Hoy - ponderado - ¡Los objetivos sólo se aplican en modo APS! - No hay datos históricos - U - Objetivo temporal - hasta - RANGO POR DEFECTO - objetivo - Tasa: %1$.2fU/h (%2$.2f%%) \nDuración %3$d min diff --git a/plugins/main/src/main/res/values-fr-rFR/strings.xml b/plugins/main/src/main/res/values-fr-rFR/strings.xml index 89c03319af..c506b61dcb 100644 --- a/plugins/main/src/main/res/values-fr-rFR/strings.xml +++ b/plugins/main/src/main/res/values-fr-rFR/strings.xml @@ -285,72 +285,7 @@ utilisation: Envoyer les fichiers journaux d\'aujourd\'hui aux développeurs avec l\'heure actuel. Situation inattendue. - - WEAR - Surveillez et contrôlez AAPS en utilisant votre montre WearOS. - (Pas de montre connectée) - État de la pompe - État de la boucle - Calc. Assistant :\nInsuline : %1$.2fU\nGlucides : %2$dg - L\'assistant rapide sélectionné n\'est plus disponible, veuillez actualiser l\'écran - Assistant : %1$s\nInsuline : %2$.2fU\nGlucides : %3$dg - Préréglage inconnu de la cible temp. : %1$s - Annuler l\'exécution des cibles Temp? - Différentes unités utilisées sur la montre et le téléphone! - Pas de Cible Temp - annuler la cible temporaire en cours? - Gly mini hors limite! - Gly maxi hors limite! - Cible temp.:\nMin: %1$s\nMax : %2$s\nDurée : %3$s - Cible temp.:\nCible: %1$s\nDurée: %2$s - Cible temp.:\nRaison: %1$s\nCible : %2$s\nDurée : %3$s - échec - veuillez vérifier le téléphone - Paramètres Wear - Commandes depuis la montre - Définir les Cibles Temp et entrer les Traitements depuis la montre. - Calculs inclus dans le résultat de l’Assistant : - Paramètres généraux - Notifier en cas de SMB - Afficher SMB sur la montre comme un bolus standard. - Paramètres du Cadran Personnalisé - Autorisation du Cadran Personnalisé - Autoriser le cadran perso chargé à modifier et bloquer certains paramètres d\'affichage de la montre pour un affichage correct du cadran - Cadran personnalisé : %1$s - Charger le cadran - Infos Cadran - Exporter le modèle - Modèle du cadran personnalisé exporté - Renvoyer toutes les données - Afficher les Paramètres sur la Montre - Liste des préférences verrouillées par le cadran - Liste des préférences requises pour le cadran - Liste des champs inclus dans le cadran Affiche une notification en cours avec un bref aperçu de ce que fait votre Boucle DONNÉES ANCIENNES - tentative de récupération des données de la pompe. - DTQ: Données encore anciennes ! Impossible de charger depuis la pompe. - g - h - Aucun profil actif! - Profil :\n\nDécalage: %1$d\nPourcentage: %2$d%%\" - %1$.2fU %1$.0f%% - Aucun profil séléctionné - S\'applique uniquement en mode APS! - Dernier résultat non disponible! - BOUCLE FERMÉE - BOUCLE OUVERTE - BOUCLE DÉSACTIVÉE - APS - Dernière exécution - Dernière injection - Aujourd’hui - pondération - Les cibles ne s\'appliquent qu\'en mode APS! - Aucune donnée d\'historique! - U - Cibles Temp - jusqu\'à - PLAGE PAR DEFAUT - cible - Taux: %1$.2fU/h (%2$.2f%%) \nDurée: %3$d min diff --git a/plugins/main/src/main/res/values-hr-rHR/strings.xml b/plugins/main/src/main/res/values-hr-rHR/strings.xml index b994647c39..ec47b38d30 100644 --- a/plugins/main/src/main/res/values-hr-rHR/strings.xml +++ b/plugins/main/src/main/res/values-hr-rHR/strings.xml @@ -120,11 +120,6 @@ Skala grafikona - - Pratite i kontrolirajte AAPS koristeći svoj WearOS sat. - (Sat nije povezan) - Loop status - Trenutni cilj:\nRazlog: %1$s\nCilj: %2$s\nTrajanje: %3$s Prikazuje tekuće obavijesti sa kratkim pregledom što loop radi diff --git a/plugins/main/src/main/res/values-hu-rHU/strings.xml b/plugins/main/src/main/res/values-hu-rHU/strings.xml index 7eab29908e..27b0f925d3 100644 --- a/plugins/main/src/main/res/values-hu-rHU/strings.xml +++ b/plugins/main/src/main/res/values-hu-rHU/strings.xml @@ -53,9 +53,6 @@ Alacsony felbontás Megjelenés - - Wear beállítások - Általános beállítások RÉGI ADAT diff --git a/plugins/main/src/main/res/values-it-rIT/strings.xml b/plugins/main/src/main/res/values-it-rIT/strings.xml index 4d8c46cd22..7fb506c704 100644 --- a/plugins/main/src/main/res/values-it-rIT/strings.xml +++ b/plugins/main/src/main/res/values-it-rIT/strings.xml @@ -285,72 +285,7 @@ uso: Invia agli sviluppatori i file log di oggi e di questo momento. Situazione inaspettata. - - SMWA - Monitora e controlla AAPS usando il tuo smartwatch WearOS. - (Nessuno smartwatch connesso) - Stato micro - Stato loop - Calc. Wizard:\nInsulina: %1$.2fU\nCHO: %2$dg - Il calcolo rapido selezionato non è più disponibile, aggiorna il riquadro - QuickWizard: %1$s\nInsulina: %2$.2fU\nCHO: %3$dg - Preset sconosciuto target temporaneo: %1$s - Cancellare i target temporanei in esecuzione? - Differenti unità usate su smartwatch e telefono! - Nessuno target temporaneo - cancellare i target temporanei in esecuzione? - Min-BG fuori range! - Max-BG fuori range! - Temptarget:\nMin: %1$s\nMax: %2$s\nDurata: %3$s - Temptarget:\nTarget: %1$s\nDurata: %2$s - Temptarget:\nMotivo: %1$s\nTarget: %2$s\nDurata: %3$s - non riuscito - controlla il telefono - Impostazioni smartwatch - Controlli da smartwatch - Imposta Temp-Target e inserisci trattamenti dallo smartwatch. - Calcoli inclusi nel risultato del Calcolatore: - Impostazioni generali - Notifica SMB - Mostra SMB sullo smartwatch come un bolo standard. - Impostazioni watchface personalizzata - Autorizzazione watchface personalizzata - Autorizza la watchface personalizzata caricata a cambiare e bloccare alcune delle impostazioni di visualizzazione dell\'orologio per adattarsi al design della watchface - Watchface personalizzata: %1$s - Carica watchface - Informazioni watchface - Esporta template - Esportato template personalizzato di Watchface - Invia di nuovo tutti i dati - Apri impostazioni sullo smartwatch - Elenco impostazioni bloccate dalla watchface - Elenco impostazioni richieste per la watchface - Elenco dei campi inclusi nella watchface Mostra una notifica persistente con una breve panoramica di ciò che sta facendo il tuo loop DATI VECCHI - tentativo di recupero dati dal micro. - TDD: Dati ancora vecchi! Non è possibile caricare dal micro. - g - h - Nessun cambio profilo attivo! - Profilo:\n\nTimeshift: %1$d\nPercentuale: %2$d%%\" - %1$.2fU %1$.0f%% - Nessun profilo caricato - Si applica solo in modalità APS! - Ultimo risultato non disponibile! - LOOP CHIUSO - LOOP APERTO - LOOP DISABILITATO - APS - Ultima esecuzione - Ultima attivazione - Oggi - ponderato - I target si applicano solo in modalità APS! - Nessun dato storico! - U - Target Temporaneo - fino a - RANGE PREDEFINITO - target - Velocità: %1$.2fU/h (%2$.2f%%) \nDurata %3$d min diff --git a/plugins/main/src/main/res/values-iw-rIL/strings.xml b/plugins/main/src/main/res/values-iw-rIL/strings.xml index fdc69555a4..20ef172cc8 100644 --- a/plugins/main/src/main/res/values-iw-rIL/strings.xml +++ b/plugins/main/src/main/res/values-iw-rIL/strings.xml @@ -286,70 +286,7 @@ שימוש: שלח קובצי יומן של היום למפתחים יחד עם זמן זה. מצב לא צפוי. - - WEAR - ניטור ושליטה ב-AndroidAPS באמצעות שעון WearOS. - (השעון לא מחובר) - סטטוס המשאבה - סטטוס הלולאה - מחשבון: %1$s\n אינס\': %2$.2f יח\'\nפחמ\': %3$d גר\' - האשף המהיר שנבחר אינו זמין, נא לרענן את האריח - אשף מהיר: %1$s\n אינס\': %2$.2f יח\'\nפחמ\': %3$d גר\' - תצורה לא ידועה של מטרה זמנית: %1$s - מבטל ערך מטרה זמני נוכחי - יחידות המידה שונות בין הטלפון והשעון! - Zero-Temp-Target - בוטל, הפעלת ערכי מטרה זמניים? - ערך הסוכר המינימלי מחוץ לטווח! - ערך הסוכר המקסימלי מחוץ לטווח! - ע\' מטרה זמני:\nמינ\': %1$s\nמקס\': %2$s\nמשך: %3$s - ע\' מטרה זמני:\nמטרה: %1$s\n משך: %2$s - ע\' מטרה זמני:\nסיבה: %1$s\nמטרה: %2$s\nמשך: %3$s - נכשל - נא לבדוק את הטלפון - הגדרות Wear - שליטה מהשעון - הגדירו ערכי מטרה זמניים וציינו טיפולים מהשעון. - חישובים הכלולים בתוצאת האשף: - הגדרות כלליות - דיווח על SMB - הצג SMB על השעון כמו בולוס סטנדרטי. - הגדרות פני שעון מותאמים אישית - אישור פני שעון מותאמים אישית - פני שעון מותאמים אישית: %1$s - טעינת פני שעון - פני שעון מידע - ייצוא תבנית - תבנית פני השעון יוצאה - שלח מחדש את כל הנתונים - פתיחת הגדרות Wear - רשימת העדפות ננעלה ע\"י פני השעון - רשימת ההעדפות הנדרשת ע\"י פני השעון - רשימת שדות הכלולים בפני השעון מציג הודעה קבועה עם סקירה קצרה של מה שהלולאה שלכם עושה נתונים ישנים - מנסה לטעון נתונים מהמשאבה. - סטטיסטיקות (TDD): נתונים ישנים! לא ניתן לטעון מהמשאבה. - גר\' - ש\' - לא הופעלה החלפת פרופיל! - %1$.2fיח\' %1$.0f% - לא נטען פרופיל - חל רק במצב APS! - התוצאה האחרונה לא זמינה! - לולאה סגורה - לולאה פתוחה - לולאה מושבתת - APS - ההפעלה האחרונה - נקבעו לאחרונה - היום - משוקלל - ערכי מטרה חלים רק במצב APS! - אין נתוני עבר! - יח\' - ערכי מטרה זמניים - עד - טווח ברירת מחדל - מטרה - מינון: %1$.2f יח\'\\שעה (%2$.2f%%) \nמשך: %3$d דק\' diff --git a/plugins/main/src/main/res/values-ko-rKR/strings.xml b/plugins/main/src/main/res/values-ko-rKR/strings.xml index a84a53a388..9ffabf8349 100644 --- a/plugins/main/src/main/res/values-ko-rKR/strings.xml +++ b/plugins/main/src/main/res/values-ko-rKR/strings.xml @@ -285,72 +285,7 @@ 사용: 예상치 못한 상황 보고를 위해 오늘의 로그 파일을 개발자에게 전송합니다. - - WEAR - WearOS 시계를 사용하여 AAPS를 모니터링하고 제어합니다. - (열결된 워치가 없습니다) - 펌프 상태 - Loop 상태 - 계산. 마법사:\n인슐린:%1$.2fU\n탄수화물: %2$dg - 선택한 빠른 마법사를 더 이상 사용할 수 없습니다, 타일을 새로고침 하십시오 - 빠른 마법사: %1$s\n인슐린: %2$.2fU\n탄수화물: %3$dg - 임시 목표 알 수 없는 사전 설정: %1$s - 임시-목표 실행을 취소하시겠습니까? - 워치와 폰과 다른 단위입니다! - 제로-임시-목표- 임시-목표 실행을 취소하시겠습니까? - 최소-BG 범위가 넘었습니다! - 최대-BG 범위가 넘었습니다! - 임시 목표:\n최소: %1$s\n최대: %2$s\n기간: %3$s - 임시 목표:\n목표: %1$s\n기간: %2$s - 임시 목표:\n이유: %1$s\n목표: %2$s\n기간: %3$s - 성공하지 못했습니다. 폰을 확인하세요 - 워치 설정 - 워치로 제어하기 - 임시목표와 관리입력을 워치로 설정합니다. - 마법사 결과에 사용 된 계산: - 일반 설정 - SMB 알림 - 일반 Bolus처럼 워치에 SMB 표시 - 사용자 지정- 워치 페이스 설정 - 사용자 지정 워치 페이스 승인 - 로드된 사용자 지정 워치 페이스를 승인하여 워치 페이스 디자인에 맞게 일부 시계 화면의 설정을 변경하고 잠급니다 - 사용자 지정 워치 페이스: %1$s - 워치 페이스 로딩하기 - 워치 페이스 정보 - 템플릿 내보내기 - 사용자 지정 워치 페이스 템플릿 내보내기 - 모든 데이터 다시 보내기 - 워치에서 설정 열기 - 워치 페이스에 의해 잠긴 선호 목록 - 워치 페이스에 필요한 선호 목록 - 워치 페이스에 포함된 필드 목록 Loop가 어떤 작동하는지에 대한 간략한 개요를 연속 알림으로 보여줍니다. 오래된 데이터 - 펌프에서 데이터를 가져오려고 합니다. - TDD: 여전히 오래된 데이터입니다! 펌프에서 로드할 수 없습니다. - g - h - 활성화된 프로파일 스위치가 없습니다! - 프로파일:\n\n시간 변화: %1$d\n백분율: %2$d%%\" - %1$.2fU %1$.0f%% - 프로파일이 로딩되지 않았습니다 - APS 모드에서만 이용 가능합니다! - 지난 결과를 이용할 수 없습니다! - CLOSED LOOP - OPEN LOOP - LOOP 비활성화 - APS - 마지막 실행 - 마지막 실행 - 오늘 - 몸무게 - 목표는 APS 모드에서만 적용 가능합니다! - 기록이 없습니다! - U - 임시 목표 - 까지 - 기본 범위 - 목표 - 비율: %1$.2fU/h (%2$.2f%%) \n기간%3$d 분 diff --git a/plugins/main/src/main/res/values-lt-rLT/strings.xml b/plugins/main/src/main/res/values-lt-rLT/strings.xml index 515d7269a6..bdfa9f23c2 100644 --- a/plugins/main/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/main/src/main/res/values-lt-rLT/strings.xml @@ -285,72 +285,7 @@ naudojimas: Siųsti šios dienos žurnalo įrašus kūrėjams dabar. Netikėta situacija. - - WEAR - Stebėti ir kontroliuoti AndroidAPS naudojant WearOS laikrodį. - (nėra prijungto laikrodžio) - Pompos statusas - Ciklo statusas - Wizard asistento skaičiuotuvas:\nInsulinas: %1$.2fU\nAngliavandeniai: %2$dg - Pasirinktas greitasis patarėjas nebepasiekiamas, atnaujinkite valdiklį - QuickWizard asistentas: %1$s\nInsulinas: %2$.2fU\nAngliavandeniai: %3$dg - Nežinomas laikino tikslo ruošinys: %1$s - Atšaukti laikinus tikslus? - Laikrodyje ir telefone naudojami skirtingi vienetai! - Zero-Temp-Target - atšaukti laikinus tikslus? - Min. cukraus kiekis nepatenka į diapozoną! - Max. cukraus kiekis nepatenka į diapozoną! - Laikinas tikslas:\nMin: %1$s\nMax: %2$s\nLaikotarpis: %3$s - Laikinas tikslas:\nTikslas: %1$s\nLaikotarpis: %2$s - LT:\npriežastis: %1$s\ntikslas: %2$s\ntrukmė: %3$s - Bandymas nesėkmingas - pasitikrinkite telefoną - Išmaniojo laikrodžio nustatymai - Laikrodžio valdikliai - Nustatyti Laikinus Tikslus ir įvesti terapinius įrašus iš laikrodžio. - Skaičiavimai, įtraukti į Patarėjo rezultatą: - Bendrieji nustatymai - Pranešti apie SMB - Rodyti SMB laikrodyje kaip standartinį bolusą. - Pasirinktiniai laikrodžio ekrano nustatymai - Pasirinktinė laikrodžio ekrano autorizacija - Leisti įkeltam pasirinktiniam ekranui keisti ir užrakinti kai kuriuos laikrodžio nustatymus, kad jie atitiktų ekrano dizainą - Pasirinktinis ekranas: %1$s - Įkelti laikrodžio ekraną - Ekrano info - Eksportuoti šabloną - Pasirinktinio ekrano šablonas eksportuotas - Pakartotinai siųsti visus duomenis - Atidaryti išmaniojo laikrodžio nustatymus - Užrakintų nustatymų sąrašas - Ekranui reikalingų nustatymų sąrašas - Į ekraną įtrauktų laukų sąrašas Rodo atsinaujinančius pranešimus su trumpa Ciklo veiklos apžvalga SENI DUOMENYS - Bandoma įkelti pompos duomenis - BPD: seni duomenys! Nepavyksta nuskaityti pompos. - g - val - Neparinktas aktyvus profilis! - Profilis:\n\nLaiko perstūmimas: %1$\nProcentai: %2$d%%\" - %1$.2fv %1$.0f%% - Profilis neįkeltas - Tik APS režime! - Paskutinis rezultatas nežinomas! - UŽDARAS CIKLAS - ATVIRAS CIKLAS - CIKLAS IŠJUNGTAS - APS - Paskutinis veiksmas - Paskutinis veiksmas - Šiandien - svertinis - Tikslai galioja tik APS režime! - Nėra istorijos! - v - Laikinas tikslas - iki - NUMATYTASIS DIAPAZONAS - tikslas - Dydis: %1$.2fv/val (%2$.2f%%) \nTrukmė %3$d min diff --git a/plugins/main/src/main/res/values-nb-rNO/strings.xml b/plugins/main/src/main/res/values-nb-rNO/strings.xml index 6632e8430a..744707877f 100644 --- a/plugins/main/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/main/src/main/res/values-nb-rNO/strings.xml @@ -156,7 +156,7 @@ Handlinger Hurtigknapper for rask tilgang til ofte brukte funksjoner - ACT + HNDL Midlertidig basal Forlenget bolus Avbryt forlenget bolus @@ -174,18 +174,18 @@ Pumpe Vis statusindikatorer på hjem-skjermen - Terskel for advarsel om alder på slangesett [h] - Terskel for kritisk alder på slangesett [h] - Terskel for advarsel, alder på insulin [h] - Terskel for kritisk alder på insulin [h] - Terskel for advarsel, alder på CGM [h] - Terskel for kritisk alder på CGM [h] + Terskel for advarsel om alder på slangesett [t] + Terskel for kritisk alder på slangesett [t] + Terskel for advarsel, alder på insulin [t] + Terskel for kritisk alder på insulin [t] + Terskel for advarsel, alder på sensor [t] + Terskel for kritisk alder på sensor [t] Terskel for advarsel, batterinivå for sensor [%] Terskel for kritisk batterinivå for sensor [%] - Terskel for advarsel, batterialder for pumpe [h] - Terskel for kritisk batterialder for pumpe [h] - Terskel for advarsel, insulinreservoar [U] - Terskel for kritisk insulinreservoar [U] + Terskel for advarsel, batterialder for pumpe [t] + Terskel for kritisk batterialder for pumpe [t] + Terskel for advarsel, insulinreservoar [E] + Terskel for kritisk insulinreservoar [E] Terskel for advarsel, batterinivå for pumpe [%] Terskel for kritisk batterinivå for pumpe [%] Kopier innstillingene fra NS @@ -242,11 +242,11 @@ Høy verdi Korte navn i menyfaner Vis merknadsfelt i dialogvindu for boluskalkulator - Bolusveiviser utfører beregninger, men bare denne del av beregnet insulin leveres. Nyttig ved bruk av SMB-algoritmen. + Boluskalkulator utfører beregninger, men bare denne del av beregnet insulin leveres. Nyttig ved bruk av SMB-algoritmen. Gi full bolus (100 %) dersom blodsukker er eldre enn Aktiver bolusveileder Bruk en påminnelse om å spise senere istedet for boluskalkulatorens resultat når blodsukker er høyt (\"pre-bolus\") - Aktiver superbolus i veiviser + Aktiver superbolus i boluskalkulator Aktiver superbolus-funksjonen i boluskalkulatoren. Ikke aktiver denne før du vet hvordan den fungerer. DEN KAN LEDE TIL EN OVERDOSERING AV INSULIN HVIS DEN BRUKES UKRITISK! Aktiver boluspåminnelse Bruk en påminnelse for å sette bolusdosen senere med boluskalkulatoren («post bolus») @@ -285,72 +285,7 @@ bruk: Send dagens loggfiler og tidspunkt til utviklere. Uventet situasjon oppstod. - - WEAR - Overvåke og kontrollere AAPS ved hjelp av WearOS-klokken. - (Ingen klokke tilkoblet) - Pumpestatus - Loopstatus - Boluskalkulator:\nInsulin: %1$.2fE\nKarbo: %2$dg - Den valgte hurtigknappen er ikke lenger tilgjengelig, oppdater klokkeflis - Hurtigknapp: %1$s\ninsulin: %2$.2fE\nKarbo: %3$dg - Ukjent forhåndsinnstilling midl. mål: %1$s - Avbryt gjeldende midl. mål? - Forskjellige enheter brukt på klokke og telefon! - Null-midl.mål - skal gjeldende midl. mål avbrytes? - Min-BS utenfor område! - Maks-BS utenfor område! - Midl. mål:\nMin: %1$s\nMaks: %2$s\nVarighet: %3$s - Midl. mål:\nMål: %1$s\nVarighet: %2$s - Midl. mål:\nÅrsak: %1$s\nMål: %2$s\nVarighet: %3$s - feilet - sjekk telefonen - Klokkeinnstillinger - Kontroller fra klokke - Sett midl. mål og angi behandlinger fra klokken. - Beregninger inkludert i resultatet fra kalkulator: - Generelle innstillinger - Varsle ved SMB - Vis SMB på klokken som en standard bolus. - Innstillinger for tilpasset klokkebakgrunn - Godkjenning for tilpasset klokkebakgrunn - Godkjenne at tilpasset klokkebakgrunn endrer AAPS- og klokkeinnstillinger i henhold til klokkebakgrunnens design - Tilpasset klokkebakgrunn: %1$s - Last inn klokkebakgrunn - Info urskive - Eksporter mal - Tilpasset klokkebakgrunn eksportert - Send alle data på nytt - Åpne Innstillinger på klokken - Liste over forhåndsvalg låst av urskive - Liste over innstillinger som kreves av klokkebakgrunnen - Liste over felt som er inkludert i urskive Viser en konstant melding med en kort oppsummering av hva loop gjør GAMLE DATA - prøver å lese data fra pumpen. - TDD: Fortsatt gamle data! Kan ikke lese fra pumpe. - g - t - Det er ikke angitt noen aktiv profil! - Profil:\n\nTidsforskyving: %1$d\nProsent: %2$d%% - %1$.2fE %1$.0f%% - Ingen profil valgt - Bare bruk i APS-modus! - Siste resultat ikke tilgjengelig! - LUKKET LOOP - ÅPEN LOOP - LOOP DEAKTIVERT - APS - Siste beregning - Siste utført - Idag - vektlagt - Mål gjelder bare i APS-modus! - Ingen historikkdata! - E - Midl. mål - inntil - STANDARD OMRÅDE - målverdi - Tilførsel: %1$.2fE/t (%2$.2f%%) \nVarighet %3$d min diff --git a/plugins/main/src/main/res/values-nl-rNL/strings.xml b/plugins/main/src/main/res/values-nl-rNL/strings.xml index 9980851ab3..cd6b2fec27 100644 --- a/plugins/main/src/main/res/values-nl-rNL/strings.xml +++ b/plugins/main/src/main/res/values-nl-rNL/strings.xml @@ -285,72 +285,7 @@ gebruik: Logboekbestanden van vandaag verzenden aan ontwikkelaars samen met de onverwachte situatie. - - WEAR - Monitor en bedien AAPS met uw WearOS horloge. - (Geen horloge verbonden) - Pomp status - Loop status - Reken. Wizard:\nInsuline: %1$.2fE\nKoolhy.: %2$dg - Geselecteerde QuickWizard is niet meer beschikbaar, vernieuw uw tegel - QuickWizard: %1$s\nInsuline: %2$.2fE\nKoolhy.: %3$dg - Tijdelijke doel onbekende preset: %1$s - Huidige tijdelijk streefdoel annuleren? - Verschillende eenheden gebruikt op horloge en telefoon! - Tijdelijk streefdoel 0 minuten, huidige tijdelijk streefdoel annuleren? - Min BG buiten bereik! - Max BG buiten bereik! - Tijdelijk streefdoel:\nMin: %1$s\nMax: %2$s\nDuur: %3$s - Tijdelijk streefdoel:\nDoel: %1$s\nDuur: %2$s - Tijdelijk streefdoel:\nReden: %1$s\nDoel: %2$s\nDuur: %3$s - Niet geslaagd - controleer de telefoon - Wear instellingen - Bedieningen via horloge - Stel tijdelijke doelen en bolussen in vanop je horloge. - Berekeningen inclusief in het resultaat van de wizard - Algemene instellingen - Waarschuw bij SMB - Toon SMB op horloge zoals gewone bolussen. - Aanpasbare Wijzerplaat Instellingen - Aanpasbare Wijzerplaat Autorisatie - Sta geladen aanpasbare wijzerplaat toe om sommige weergave-instellingen van horloge te wijzigen en te vergrendelen voor het ontwerp van de wijzerplaat - Aanpasbare Watchface: %1$s - Laad Watchface - Watchface informatie - Exporteer template - Aanpasbare watchface template geëxporteerd - Update Wear gegevens - Open instellingen op Wear - Lijst met instellingen die zijn vergrendeld door watchface - Lijst met instellingen vereist voor watchface - Lijst van velden opgenomen in de watchface Toont een permanente melding met een beknopt overzicht van hetgeen de Loop momenteel doet Oude gegevens - proberen gegevens van de pomp op te halen. - TDD: Nog steeds oude gegevens! Kan niet laden van de pomp. - g - u - Geen actieve profielwissel! - Profiel:\n\nTijdverschuiving: %1$d\nPercentage: %2$d%%\" - %1$.2fE %1$.0f%% - Geen profiel geladen - Alleen gebruiken in de APS modus! - Laatste resultaat niet beschikbaar! - GESLOTEN LOOP - OPEN LOOP - LOOP UITGESCHAKELD - APS - Laatst uitgevoerd - Laatste uitvoering - Vandaag - gewogen - Streefdoelen zijn alleen van toepassing in de APS modus! - Geen geschiedenisgegevens! - E - Tijdelijk streefdoel - tot - Standaard streefbereik - streefwaarde - Basaal: %1$.2fE/uur (%2$.2f%%) \nDuur %3$d min diff --git a/plugins/main/src/main/res/values-pl-rPL/strings.xml b/plugins/main/src/main/res/values-pl-rPL/strings.xml index 3dfe87e9d2..6736e9fe1d 100644 --- a/plugins/main/src/main/res/values-pl-rPL/strings.xml +++ b/plugins/main/src/main/res/values-pl-rPL/strings.xml @@ -285,72 +285,7 @@ użytkowanie: Wyślij dzisiejsze pliki logów razem z datą i czasem do programistów. Nieoczekiwana sytuacja. - - WEAR - Monitoruj i steruj AAPS używając zegarka z WearOS. - (Brak połączonego zegarka) - Status pompy - Status pętli - Kalkulator:\nInsulina: %1$.2fU\nWęgle: %2$dg - Wybrany szybki bolus nie jest już dostępny, odśwież swój kafelek - Szybki bolus: %1$s\nInsulina: %2$.2fU\nWęgle: %3$dg - Nieznane ustawienie celu tymczasowego: %1$s - Anulować bieżący cel tymczasowy? - Różne jednostki używane na zegarku i telefonie! - Zerowy cel tymczasowy - anulować bieżący cel tymczasowy? - Min-BG poza zakresem! - Max-BG poza zakresem! - Cel tymczasowy:\nMin: %1$s\nMax: %2$s\nCzas trwania: %3$s - Cel tymczasowy:\nCel: %1$s\nCzas trwania: %2$s - Cel tymczasowy:\nPowód: %1$s\nCel: %2$s\nCzas trwania: %3$s - nie udało się - proszę sprawdzić telefon - Ustawienia Wear - Sterowanie z zegarka - Ustawiaj wartości docelowe i wprowadzaj leczenie z zegarka. - Obliczenia uwzględnione w wynikach kreatora: - Ustawienia ogólne - Powiadom na SMB - Pokaż SMB na zegarku jak bolus standardowy. - Ustawienia niestandardowej tarczy - Uprawnienia niestandardowej tarczy - Upoważnij załadowaną niestandardową tarczę zegarka, aby mogła zmieniać i zablokować niektóre ustawienia wyświetlacza zegarka w celu dopasowania ich do tarczy - Niestandardowa tarcza: %1$s - Załaduj Tarczę - O tarczy - Eksportuj szablon - Szablon tarczy niestandardowej wyeksportowany - Prześlij ponownie wszystkie dane - Otwórz ustawienia dla Wear - Lista opcji zablokowanych przez tarczę - Lista opcji wymaganych przez tarczę - Lista pól dołączonych do tarczy Wyświetla bieżące powiadomienia z krótkim omówieniem działania pętli NIEAKTUALNE DANE - próbuję pobrać dane z pompy. - TDD: Nadal stare dane! Nie można załadować z pompy. - g - h - Nie ustawiono aktywnego profilu! - Profil:\n\nZmiana czasu: %1$d\nProcent: %2$d%%\" - %1$.2fU %1$.0f%% - Nie załadowano profilu - Stosuje się tylko w trybie APS! - Ostatni wynik niedostępny! - ZAMKNIĘTA PĘTLA - OTWARTA PĘTLA - PĘTLA WYŁĄCZONA - APS - Ostatnie uruchomienie - Ostatnie działanie - Dziś - ważone - Cele mają zastosowanie tylko w trybie APS! - Brak danych historycznych! - U - Cel tymczasowy - do - ZAKRES DOMYŚLNY - cel - Dawka: %1$.2fU/h (%2$.2f%%) \nCzas trwania %3$d min diff --git a/plugins/main/src/main/res/values-pt-rBR/strings.xml b/plugins/main/src/main/res/values-pt-rBR/strings.xml index b2871ff5ba..06d961821e 100644 --- a/plugins/main/src/main/res/values-pt-rBR/strings.xml +++ b/plugins/main/src/main/res/values-pt-rBR/strings.xml @@ -286,72 +286,7 @@ Uso: Enviar os ficheiros de registo do dia de hoje para os programadores. Situação inesperada. - - WEAR - Monitore e controle AndroidAPS usando seu relógio WearOS. - (Nenhum relógio conectado) - Estado da Bomba - Status do loop - Calculadora:\nInsulin: %1$.2fU\nCarbs: %2$dg - O assistente rápido selecionado não está mais disponível, atualize seu atalho - Assistente rápido: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg - Alvo temporário pré-definido desconhecido: %1$s - Cancelando Alvos temporários em execução? - Unidades diferentes usadas no relógio e telefone! - Desligar alvo temporário - cancelando Alvos temporários atuais? - Glicemia mínima fora do alvo! - Glicemia maxima fora da meta! - Alvo temporário:\nMin: %1$s\nMax: %2$s\nDuração: %3$s - Alvo temporário:\nTarget: %1$s\nDuration: %2$s - Tempo no alvo:\nMotivo: %1$s\nAlvo: %2$s\nDuração: %3$s - não foi bem sucedido - por favor, verifique o telefone - Definições Wear - Controles do Relógio - Definir Alvo-Temp and inserir Tratamentos do relógio. - Resultado cálculos incluídos no Assistente: - Configurações gerais - Notificar no SMB - Mostrar SMB no relogio como bolus normal. - Configurações personalizadas de Watchface - Autorização de Watchface Personalizada - Autorizar watchface carregado e carregado para alterar e bloquear algumas configurações de relógio para o design do watchface - Watchface Personalizado: %1$s - Carregar Watchface - Infos Watchface - Exportar modelo - Modelo de watchface personalizado exportado - Reenviar Todos os Dados - Abrir Definições em Wear - Lista de preferências bloqueadas pela Watchface - Lista de preferências solicitadas pela Watchface - Lista de campos incluídos na Watchface Mostra uma notificação em curso com um breve resumo do que o seu loop está a fazer DADOS ANTIGOS - tentando buscar dados da Bomba. - TDD: Dados ainda antigos! Não é possível carregar da bomba. - g - h - Nenhum perfil ativo definido! - Perfil:\n\nFuso: %1$d\nPorcentagem: %2$d%%\" - %1$.2f / %1$.0f U%% - Nenhum perfil selecionado - Aplique somente no modo APS! - Último resultado indisponível! - LOOP FECHADO - LOOP ABERTO - LOOP DESATIVADO - APS - Última execução - Ultima execução - Hoje - ponderado - Alvos só se aplicam no modo APS! - Nenhum dado no histórico! - U - Alvos Temporários - até - DEFAULT RANGE - alvo - Taxa: %1$.2fU/h (%2$.2f%%) \nDuração %3$d min diff --git a/plugins/main/src/main/res/values-pt-rPT/strings.xml b/plugins/main/src/main/res/values-pt-rPT/strings.xml index dcf7c28303..f4dd41c82c 100644 --- a/plugins/main/src/main/res/values-pt-rPT/strings.xml +++ b/plugins/main/src/main/res/values-pt-rPT/strings.xml @@ -285,48 +285,7 @@ utilização: Enviar os ficheiros de registo do dia de hoje para os programadores. Situação inesperada. - - WEAR - Monitorizar e controlar a AndroidAPS utilizando o seu relógio WearOS. - (Nenhum relógio Conectado) - Estado da Bomba - Estado do loop - Calc. Assistente:\nInsulina: %1$.2fU\nHidratos: %2$dg - O assistente rápido selecionado não está mais disponível, atualize o ecrã - Assistente rápido: %1$s\nInsulina: %2$.2fU\nHidratos: %3$dg - Predefinição de alvo temporário desconhecido: %1$s - Cancelando Alvos Temporários em execução? - Diferentes unidades usadas no relógio e telefone! - Alvo-temporário-Zero - cancelar Alvos -Temporários em progresso? - Min-GLIC fora do alvo! - Máx-GLIC fora do alvo! - AlvoTemporário:\nMin: %1$s\nMax: %2$s\nDuração: %3$s - AlvoTemporário:\nAlvo: %1$s\nDuração: %2$s - AlvoTemporário:\nMotivo %1$s\nAlvo: %2$s\nDuração: %3$s - sem efeito - por favor verifique no telemóvel - Definições do Relógio - Controles do Relógio - Definir Alvo-Temp and inserir Tratamentos do relógio. - Resultado cálculos incluídos no Assistente: - Definições Gerais - Notificar no SMB - Mostrar SMB no relogio como bolus normal. - Definições da watchface predefinida - Autorização da watchface predefinida - Autorizar watchface por definição para alterar e bloquear algumas configurações de exibição do relógio para se adequarem ao design do watchface - Watchface Predefinida: %1$s - Carregar Watchface - Infos Watchface - Exportar modelo - Modelo de Watchface Predefinida exportado - Reenviar Todos os Dados - Abrir Definições no Relógio - Lista de preferências bloqueadas pela Watchface - Lista de preferências necessárias para a Watchface - Lista de campos incluídos na Watchface Mostra uma notificação em curso com um breve resumo do que o seu loop está a fazer DADOS ANTIGOS - TID: Ainda dados antigos! Não é possível carregar a partir da bomba. - %1$.2fU %1$.0f%% diff --git a/plugins/main/src/main/res/values-ro-rRO/strings.xml b/plugins/main/src/main/res/values-ro-rRO/strings.xml index a0b2e86bad..9e3ce4e696 100644 --- a/plugins/main/src/main/res/values-ro-rRO/strings.xml +++ b/plugins/main/src/main/res/values-ro-rRO/strings.xml @@ -286,72 +286,7 @@ folosire: Trimite înregistrările zilei de astăzi către dezvoltatori, împreună cu timpul curent. Situație neașteptată. - - CEAS - Monitorizează și controlează AAPS folosind ceasul WearOS. - (Niciun ceas conectat) - Stare pompă - Stare buclă - Calc. Asistent:\nInsulină: %1$.2fU\nCarburi: %2$dg - Asistentul rapid selectat nu mai este disponibil, vă rugăm să reîncărcați iconița - Asistent calcul: %1$s\nInsulină: %2$.2fU\nCarbohidrați: %3$dg - Presetare ȚintăTemporară necunoscută: %1$s - Se anulează rularea țintelor temporare? - Unități diferite folosite pe ceas și telefon! - Zero-Temp-Target - anulați ȚinteleTemporare actuale? - Min-BG în afara intervalului! - Max-BG în afara intervalului! - ȚintăTemporară:\nMin: %1$s\nMax: %2$s\nDurată: %3$s - ȚintăTemporară:\nȚintă: %1$s\nDurată: %2$s - ȚintăTemporarăt:\nMotiv: %1$s\nȚintă: %2$s\nDurată: %3$s - fără succes - verificați telefonul - Setări Wear - Controlare din ceas - Setare Ținte-Temporare și se introduc Tratamente din ceas. - Calcule incluse în rezultatul asistentului: - Setări generale - Notifică despre SMB - Arată SMB pe ceas ca și un bolus standard. - Cadran ceas - Setări personalizate - Autorizare cadran personalizat - Autorizează cadranul personalizat să modifice și să blocheze unele setări de afișare ale ceasului pentru a se potrivi designului cadranului - Cadran personalizat: %1$s - Încărcaţi cadranul - Info cadran - Exportă șablon - Șablon ceas personalizat exportat - Retrimite toate datele - Deschide setările pe Wear - Lista de preferințe blocate de cadranul ceasului - Lista de preferințe solicitate de cadranul ceasului - Lista câmpurilor incluse în fața ceasului Afişează o notificare activă cu o scurtă descriere referitoare la starea buclei DATE VECHI - se încearcă preluarea datelor din pompă. - TDD: Date încă vechi! Nu se poate încărca din pompă. - g - h - Nicio schimbare de profil activă! - Profil:\n\nDecalare: %1$d\nProcent: %2$d%%\" - %1$.2fU %1$.0f%% - Niciun profil încărcat - Aplicați doar în modul APS! - Ultimul rezultat nu este disponibil! - BUCLĂ ÎNCHISĂ - BUCLĂ DESCHISĂ - BUCLĂ INACTIVĂ - APS - Ultima rulare - Ultima implementare - Astăzi - ponderat - Țintele se aplică numai în modul APS! - Nu există date istorice! - U - Țintă temporară - până la - INTERVAL IMPLICIT - țintă - Rată: %1$.2fU/h (%2$.2f%%) \nDurată %3$d min diff --git a/plugins/main/src/main/res/values-ru-rRU/strings.xml b/plugins/main/src/main/res/values-ru-rRU/strings.xml index 9347813a0d..2682e419e0 100644 --- a/plugins/main/src/main/res/values-ru-rRU/strings.xml +++ b/plugins/main/src/main/res/values-ru-rRU/strings.xml @@ -285,72 +285,7 @@ применение: Отправьте файлы сегодняшнего лога разработчикам наряду с этим. Непредвиденная ситуация. - - WEAR - Мониторить и контролировать AAPS при помощи часов WearOS. - (Часы не подключены) - Статус помпы - Статус цикла - Мастер:\nИнсулин: %1$.2fед\nУгл: %2$dг - Выбранный мастер быстрого доступа больше недоступен, обновите плитку - Мастер: %1$s\nИнсулин: %2$.2fЕд\nУгл: %3$dg - Неизвестная конфигурация врем цели: %1$s - Отменить врем цели? - На часах и телефоне различные единицы измерения! - Нулевая врем цель - отмена? - Мин ГК вне диапазона! - Макс ГК вне диапазона! - ВремЦель:\nМин: %1$s\nМакс.: %2$s\nДлительность: %3$s - ВремЦель:\nЦель: %1$s\nДлительность: %2$s - ВремЦель:\nПричина: %1$s\nЦель.: %2$s\nДлительность: %3$s - неуспешно - проверьте телефон - настройки смарт-часов Wear - Контроль с часов - Ставить временные цели и вводить терапию с часов. - Расчеты включены в результат калькулятора: - Общие настройки - Сообщить о супер микро болюсе SMB - Показывать супер микро болюс SMB на часах как стандартный болюс. - Настройка циферблатов - Авторизация пользовательских циферблатов - Авторизовать загруженные пользовательские циферблаты для изменения и блокировки некоторых параметров отображения часов в соответствии с дизайном часов - Пользовательский циферблат %1$s - Загрузить циферблат - Циферблат Infos - Экспортировать шаблон - Пользовательский шаблон циферблата экспортирован - повторить отправку всех данных - Открыть настройки на Wear - Список настроек, блокируемых циферблатом - Список настроек, требующихся для циферблата - Список полей, входящих в циферблат Показывает текущие уведомления и краткий обзор событий цикла старые данные - пытаюсь получить данные с помпы. - TDD: До сих пор старые данные! Невозможно загрузить с помпы. - г - ч - Активный профиль не установлен! - Профиль:\n\nСдвиг по времени: %1$d$\nПроцент: %2$d%%\" - %1$.2fед%1$.0f%% - Профиль не загружен - Применять только в режиме APS! - Последний результат недоступен! - ЗАМКНУТЫЙ ЦИКЛ - ОТКРЫТЫЙ ЦИКЛ - ЦИКЛ ВЫКЛЮЧЕН - APS - Предыдущее выполнение - Предыдущая активация - Сегодня - взвешенный - Цели применяются только в режиме APS! - Нет данных истории! - Ед - Врем. цель - до - ДИАПАЗОН ПО УМОЛЧАНИЮ - целевое значение ГК: - Скорость: %1$.2fед/ч (%2$.2f%%) \nПродолжительность %3$d мин diff --git a/plugins/main/src/main/res/values-sk-rSK/strings.xml b/plugins/main/src/main/res/values-sk-rSK/strings.xml index 3094af3305..cc0f48fe51 100644 --- a/plugins/main/src/main/res/values-sk-rSK/strings.xml +++ b/plugins/main/src/main/res/values-sk-rSK/strings.xml @@ -286,72 +286,7 @@ používanie: Odošlite dnešné súbory protokolov vývojárom spolu s týmto časom. Neočakávaná situácia. - - WEAR - Zobrazovanie stavu a riadenie AndroidAPS z hodiniek s WearOS. - (Žiadne hodinky nie sú pripojené) - Stav pumpy - Stav uzavretého okruhu - Kalkulačka: \nInzulín: %1$.2fJI\nSacharidy: %2$dg - Vybraný rýchly bolus už nie je k dispozícii, obnovte prosím dlaždicu - Rýchly bolus: %1$s\nInzulín: %2$.2fJI\nSacharidy: %3$dg - Dočasný cieľ neznáma predvoľba: %1$s - Zrušenie bežiaceho dočasného cieľa? - Použité rozdielne jednotky v hodinkách a v telefóne! - Nulový dočasný cieľ - zrušenie bežiaceho dočasného cieľa? - Minimálna glykémia mimo rozsah! - Maximálna glykémia mimo rozsah! - Doč. cieľ:\nMin: %1$s\nMax: %2$s\nTrvanie: %3$s - Doč. cieľ:\nCieľ: %1$s\nTrvanie: %2$s - Doč. cieľ:\nDôvod: %1$s\nCieľ: %2$s\nTrvanie: %3$s - Neúspešné - skontrolujte telefón - Nastavenie hodiniek - Ovládanie z hodiniek - Nastavovanie dočasných cieľov a vkladanie ošetrení hodinkami. - Kalkulácia použitá vo výsledku wizardu: - Všeobecné nastavenia - Oznámenie pri SMB - Ukazovať SMB na hodinkách ako normálny bolus. - Nastavenie vlastného ciferníka - Autorizácia vlastného ciferníka - Autorizujte načítaný vlastný ciferník, aby se zmenili a uzamkli niektoré nastavenia hodiniek tak, aby vyhovovali designu ciferníka - Vlastný ciferník: %1$s - Nahrať ciferník - Informácie o ciferníku - Exportovať šablónu - Vlastná šablóna ciferníka exportovaná - Všetky dáta poslať znova - Otvoriť nastavenia na hodinkách - Zoznam preferencií uzamknutý hodinkami - Zoznam preferencií potrebných pre ciferník - Zoznam polí zahrnutých do ciferníka Zobrazuje priebežné oznámenia v Androide s krátkym prehľadom, čo práve uzavretý okruh robí ZASTARALÉ DÁTA - pokus o načítanie dát z pumpy. - CDD: Stále staré dáta! Nie je možné načítať z pumpy. - g - h - Nie je nastavený žiadny aktívny profil! - Profil:\n\nPosunutie: %1$d\nPercento: %2$d%%\" - %1$.2fJI %1$.0f%% - Nie je vybraný žiadny profil - Použiť iba v APS móde! - Posledný výsledok nie je k dispozícii! - UZAVRETÝ OKRUH - OTVORENÝ OKRUH - UZAVRETÝ OKRUH DEAKTIVOVANÝ - APS - Posledné spustenie - Naposledy vykonané - Dnes - vážený - Ciele sa použijú iba v režime APS! - Žiadne údaje o histórii! - J - Dočasný cieľ - až do - ŠTANDARDNÝ ROZSAH - cieľ - Rýchlosť: %1$.2fJI/h (%2$.2f%%) \nTrvanie %3$d min diff --git a/plugins/main/src/main/res/values-sr-rCS/strings.xml b/plugins/main/src/main/res/values-sr-rCS/strings.xml index e99899991f..39887ea1db 100644 --- a/plugins/main/src/main/res/values-sr-rCS/strings.xml +++ b/plugins/main/src/main/res/values-sr-rCS/strings.xml @@ -73,8 +73,6 @@ Tretmani - - Pratite i kontrolirajte AAPS koristeći svoj WearOS sat. Prikazuje tekuće obaveštenje sa kratkim pregledom onoga što vaša petlja radi diff --git a/plugins/main/src/main/res/values-sv-rSE/strings.xml b/plugins/main/src/main/res/values-sv-rSE/strings.xml index 6d03304326..6bb8bf6d80 100644 --- a/plugins/main/src/main/res/values-sv-rSE/strings.xml +++ b/plugins/main/src/main/res/values-sv-rSE/strings.xml @@ -286,72 +286,7 @@ användning: Skicka dagens loggfiler till utvecklarna tillsammans med denna datumstämpel. Oväntad situation. - - Wear - Följ och kontrollera AAPS med din Wear OS-klocka. - (Ingen klocka ansluten) - Pumpstatus - Loop-status - Kalkylator:\nInsulin: %1$.2fU\nKolhydrater: %2$dg - Vald snabbknapp inte längre tillgänglig. Vänligen uppdatera vyn - Snabbsteg: %1$s\nInsulin: %2$.2fU\nKolhydrater: %3$dg - Tempmål okänd förinställning: %1$s - Avbryt temp-mål? - Olika enheter på klocka och telefon! - Noll-temp - avbyta nuvarande temp-mål? - Ogiltigt minimum BG! - Ogiltigt maximum BG! - Temp-mål:\nMin: %1$s\nMax: %2$s\nVaraktighet: %3$s - Temp-mål:\nMål: %1$s\nDuration: %2$s - Tillfälligt mål:\nOrsak: %1$s\nMål: %2$s\nVaraktighet: %3$s - misslyckat - kontrollera telefonen - Inställningar för klocka (Wear) - Kontrollera från klockan - Sätt temp målvärde och ange behandlingar från klockan. - Kalkyler inkluderade i resultatet - Generella inställningar - Skicka notis vid SMB - Visa SMB på klockan som en standardbolus. - Anpassade inställningar för urtavla - Anpassad auktorisering för urtavla - Auktorisera inlästa anpassade urtavlor för att ändra och låsa vissa visningsinställningar för att passa design av urtavlan - Anpassad urtavla: %1$s - Ladda urtavla - Info urtavla - Exportera mall - Anpassad mall exporterad - Uppdatera klockans data - Öppna inställningar på klockan - Inställningar som är låsta av urtavlan - Inställningar som krävs av urtavlan - Fält som ingår i urtavlan Visar en konstant avisering med en kort sammanfattning av vad din loop gör Aktuellt BG saknas! - försöker hämta data från pump. - TDD: Fortfarande gamla data! Kan inte hämta från pump. - g - h - Ingen aktiv profil! - Profil:\n\nTidsförskjutning: %1$d\nProcent: %2$d%%\" - %1$.2fU %1$.0f%% - Ingen profil inläst - Använd endast i APS-läge! - Senaste resultat ej tillgängligt! - CLOSED LOOP - OPEN LOOP - LOOP INAKTIVERAD - APS - Kördes senast - Senaste handling - Idag - viktad - Målen gäller endast i APS-läge! - Inget historiskt data! - U - Tillfälligt målvärde - tills - STANDARDINTERVALL - målvärde - Dos: %1$.2fU/h (%2$.2f%%) \nVaraktighet %3$d min diff --git a/plugins/main/src/main/res/values-tr-rTR/strings.xml b/plugins/main/src/main/res/values-tr-rTR/strings.xml index 1a95f1180f..5d6cb29c17 100644 --- a/plugins/main/src/main/res/values-tr-rTR/strings.xml +++ b/plugins/main/src/main/res/values-tr-rTR/strings.xml @@ -286,72 +286,7 @@ kullanım: Bu günün kayıt dosyalarını geliştiricilere gönderin. Beklenmedik bir durum. - - WEAR - WearOS saatinizi kullanarak AAPS\'yi izleyin ve kontrol edin. - (Saat Bağlı Değil) - Pompa durumu - Döngü durumu - Hesap Mak.:\nİnsulin: %1$.2fÜ\nKarb: %2$dg - Seçili hızlı asistan artık mevcut değil, lütfen kutucuğu yenileyin - Hızlı Asistan: %1$s\nİnsülin: %2$.2fU\nKarb: %3$dg - Geiçici hedef bilinmeyen ön ayarı: %1$s - Çalışan Geçici-Hedefler iptal edilsin mi? - Saatte ve telefonda farklı birimler kullanılıyor! - Sıfır-Geçici-Hedef - Çalışan Geçici-Hedefler iptal edilsin mi? - Min-KŞ aralık dışında! - Maks-KŞ aralık dışında! - Geçici Hedef:\nMin: %1$s\nMaks: %2$s\nSüre: %3$s - Geçici Hedef:\nHedef: %1$s\nSüre: %2$s - Geçici Hedef:\nNeden: %1$s\nHedef: %2$s\nSüre: %3$s - başarısız - lütfen telefonu kontrol edin - Wear ayarları - Saat tarafından kontrol - Tedavileri ve Geçici hedefleri saat tarafından girin. - Sihirbaz sonucuna dahil edilen hesaplamalar: - Genel Ayarlar - SMB\'yi bildir - Saatte SMB\'yi standart bir bolus gibi göster. - Özel Saat arayüzü Ayarları - Özel Saat arayüzü Yetkilendirmesi - Bazı saat ekranı ayarlarını saatarayüzü tasarımına uyacak şekilde değiştirmek ve kilitlemek için, yüklenen özel saat arayüzüne yetki verin - Özel Saat arayüzü: %1$s - Saat arayüzü yükle - Saat arayüzü bilgisi - Şablonu Dışarı Aktar - Saat arayüzü şablonu dışa aktarıldı - Tüm verileri yeniden gönderin - Wear\'de ayarları aç - Saat arayüzü tarafından kilitlenen tercihlerin listesi - Saat arayüzü için gerekli tercihlerin listesi - Saat arayüzüne dahil edilen alanların listesi Döngü\'ün ne yaptığını kısa bir genel bakışla devam eden bir bildirimi gösterir ESKİ VERİ - pompadan veri almaya çalışıyor. - TGD: Hala eski veriler! Pompadan yüklenemiyor. - gr - sa - Etkin profil ayarlanmadı! - Profil:\n\nZaman Kayması: %1$d\nYüzde: %2$d%%\" - %1$.2fÜ %1$.0f%% - Profil yüklenmedi - Sadece APS modunda uygulayın! - Son sonuç mevcut değil! - KAPALI DÖNGÜ - AÇIK DÖNGÜ - DÖNGÜ DEVRE DIŞI - APS (YPS) - Son Çalıştırma - Son sahne - Bugün - ağırlıklı - Hedefler yalnızca APS modunda uygulanır! - Geçmiş verisi yok! - Ü - Geçici Hedef - kadar - VARSAYILAN ARALIK - hedef - Oran: %1$.2fÜ/sa (%2$.2f%%) \nSüre %3$d dk diff --git a/plugins/main/src/main/res/values-uk-rUA/strings.xml b/plugins/main/src/main/res/values-uk-rUA/strings.xml index 480ca1744d..4ba380ff88 100644 --- a/plugins/main/src/main/res/values-uk-rUA/strings.xml +++ b/plugins/main/src/main/res/values-uk-rUA/strings.xml @@ -9,6 +9,5 @@ - diff --git a/plugins/main/src/main/res/values-zh-rCN/strings.xml b/plugins/main/src/main/res/values-zh-rCN/strings.xml index 7e0e457535..089ede3193 100644 --- a/plugins/main/src/main/res/values-zh-rCN/strings.xml +++ b/plugins/main/src/main/res/values-zh-rCN/strings.xml @@ -269,31 +269,6 @@ 皮肤 发送包括当前时间的今日的日志文件给开发者。描述一下意外情况 - - 手表 - 闭环状态 - 计算. 向导:\n胰岛素: %1$.2fU\n碳水: %2$d克 - 选定的快速向导不再可用,请刷新 - 快速向导: %1$s\n胰岛素: %2$.2fU\n碳水: %3$d克 - 未预设的临时目标: %1$s - 取消正在运行的临时目标? - 手表和手机上使用了不同的单位! - 无临时目标-取消正在运行的临时目标? - 目标血糖最小值超出范围! - 目标血糖最大值超出范围! - 临时目标:\n最小: %1$s\n最大: %2$s\n持续时间: %3$s - 临时目标:\n目标: %1$s\n持续时间: %2$s - 临时目标:\n原因: %1$s\n目标: %2$s\n持续时间: %3$s - 未成功-请检查手机 - 手表设置 - 从手表上控制 - 设置临时目标并从手表中进行治疗操作。 - 包含在向导中的计算结果: - 常规设置 - 在 SMB 上通知 - 在手表上像显示常规大剂量一样显示SMB微型大剂量 - 重新发送所有数据 - 在手表上打开设置 显示持续的通知, 其中简要概述了您的闭环正在做什么 旧数据 diff --git a/plugins/main/src/main/res/values/strings.xml b/plugins/main/src/main/res/values/strings.xml index 9f1b4c6556..a46b55b81e 100644 --- a/plugins/main/src/main/res/values/strings.xml +++ b/plugins/main/src/main/res/values/strings.xml @@ -330,76 +330,9 @@ Send today\'s log files to developers along with this time. Unexpected situation. IobCobCalculator - - WEAR - Monitor and control AAPS using your WearOS watch. - (No Watch Connected) - Pump status - Loop status - Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg - Selected quickwizard no longer available, please refresh your tile - QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg - Temptarget unknown preset: %1$s - Cancelling running Temp-Targets? - Different units used on watch and phone! - Zero-Temp-Target - cancelling running Temp-Targets? - Min-BG out of range! - Max-BG out of range! - Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s - Temptarget:\nTarget: %1$s\nDuration: %2$s - Temptarget:\nReason: %1$s\nTarget: %2$s\nDuration: %3$s - not successful - please check phone - Wear settings - Controls from Watch - Set Temp-Targets and enter Treatments from the watch. - Calculations included in the Wizard result: - General Settings - Notify on SMB - Show SMB on the watch like a standard bolus. - Custom Watchface Settings - Custom Watchface Authorization - Authorize loaded custom watchface to change and lock some watch display settings to suit watchface design - Custom Watchface: %1$s - Load Watchface - Infos Watchface - Export template - Custom watchface template exported - Resend All Data - Open Settings on Wear - List of prefs locked by the Watchface - List of prefs required for the Watchface - List of fields included into the Watchface - - Ongoing Notification Shows an ongoing notification with a short overview of what your loop is doing OLD DATA - trying to fetch data from pump. - TDD: Still old data! Cannot load from pump. - g - h - No active profile switch! - Profile:\n\nTimeshift: %1$d\nPercentage: %2$d%%\" - %1$.2fU %1$.0f%% - No profile loaded - Only apply in APS mode! - Last result not available! - CLOSED LOOP - OPEN LOOP - LOOP DISABLED - APS - Last run - Last Enact - Today - weighted - Targets only apply in APS mode! - No history data! - U - Temp Target - until - DEFAULT RANGE - target - Rate: %1$.2fU/h (%2$.2f%%) \nDuration %3$d min diff --git a/plugins/sensitivity/src/main/res/values-nb-rNO/strings.xml b/plugins/sensitivity/src/main/res/values-nb-rNO/strings.xml index 623b93010d..0d1a525bf3 100644 --- a/plugins/sensitivity/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/sensitivity/src/main/res/values-nb-rNO/strings.xml @@ -9,15 +9,15 @@ Sensitivitet beregnes som en vektet gjennomsnittsverdi av avvikene. Ferske avvik har høyere vekting. Minimum opptak av karbohydrater beregnes ut fra maks opptakstid for karbohydrater angitt i dine innstillinger. Denne algoritmen er den raskeste for å justere endringer i sensitivitet. UAM deaktivert fordi den trenger Oref1 sensitivitetsplugin Absorberingsinnstillinger - Maks absorberingstid for måltid [h] + Maks absorberingstid for måltid [t] Tid i timer hvor det forventes at alle karbohydrater fra måltid vil være absorbert - Intervall for autosens [h] + Intervall for autosens [t] Antall timer med historiske data for beregning av sensitivitet (absorpsjonstid for KH er ekskludert) Standardverdi: 1.2\nDette er en multiplikatorbegrensning for autosens (og snart autotune) som begrenser at autosens ikke kan øke med mer enn 20%%, som dermed begrenser hvor mye autosens kan justere opp dine basaler, hvor mye ISF kan reduseres og hvor lavt BS målverdi kan settes. Standardverdi: 0.7\nDette er en multiplikatorbegrensning for autosens-sikkerhet. Den begrenser autosens til å redusere basalverdier, og øke isulinssensitivitet (ISF) og BS mål med ikke mer enn enn 30%. Maks autosens ratio Minimum autosens ratio Standardverdi er: 3.0 (AMA) eller 8.0 (SMB). Dette er grunninnstillingen for KH-opptak per 5 minutt. Den påvirker hvor raskt COB skal reduseres, og benyttes i beregning av fremtidig BS-kurve når BS enten synker eller øker mer enn forventet. Standardverdi er 3mg/dl/5 min. - Maks absorpsjonstid for måltid [h] + Maks absorpsjonstid for måltid [t] Etter denne tiden forventes det at måltidet er absorbert. Eventuelle gjenværende karbo vil tas ut av beregninger. diff --git a/plugins/source/src/main/res/values-nb-rNO/strings.xml b/plugins/source/src/main/res/values-nb-rNO/strings.xml index 1d8deba163..f943044097 100644 --- a/plugins/source/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/source/src/main/res/values-nb-rNO/strings.xml @@ -34,6 +34,6 @@ I xDrip+, velg 640G/Eversens som datakilde Innstillinger for opplasting av BS Logg sensorbytte til NS - Opprett hendelse \"Sensor bytte\" automatisk i NS ved start av sensoren + Opprett hendelse \"Sensorbytte\" automatisk i NS ved start av sensoren retning diff --git a/plugins/sync/build.gradle b/plugins/sync/build.gradle index 7fdb1dbd8f..dc8826f9ba 100644 --- a/plugins/sync/build.gradle +++ b/plugins/sync/build.gradle @@ -19,8 +19,9 @@ dependencies { implementation project(':shared:impl') implementation project(':database:entities') implementation project(':database:impl') - implementation project(':core:main') + implementation project(':core:graphview') implementation project(':core:interfaces') + implementation project(':core:main') implementation project(':core:nssdk') implementation project(':core:ui') implementation project(':core:utils') @@ -44,9 +45,11 @@ dependencies { api("io.socket:socket.io-client:2.1.0") api "com.squareup.okhttp3:okhttp:$okhttp3_version" api "com.squareup.okhttp3:logging-interceptor:$okhttp3_version" - //api "com.squareup.retrofit2:retrofit:$retrofit2_version" api "com.squareup.retrofit2:adapter-rxjava3:$retrofit2_version" api "com.squareup.retrofit2:converter-gson:$retrofit2_version" api "com.google.code.gson:gson:$gson_version" + + // DataLayerListenerService + api "com.google.android.gms:play-services-wearable:$play_services_wearable_version" } \ No newline at end of file diff --git a/plugins/sync/src/main/AndroidManifest.xml b/plugins/sync/src/main/AndroidManifest.xml index a4afffcc49..7a683c85be 100644 --- a/plugins/sync/src/main/AndroidManifest.xml +++ b/plugins/sync/src/main/AndroidManifest.xml @@ -8,6 +8,10 @@ android:name=".nsclient.services.NSClientService" android:enabled="true" android:exported="false" /> + { + val byteBuffer: ByteBuffer = ByteBuffer.wrap(data) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + byteBuffer.limit(toLongBoundary(end)) + val buffer: LongBuffer = byteBuffer.asLongBuffer() + val encodedData: MutableList = ArrayList(buffer.limit()) + while (buffer.position() < buffer.limit()) { + encodedData.add(buffer.get()) + } + return encodedData + } + + fun encodedBase64(): String { + val byteBuffer: ByteBuffer = ByteBuffer.wrap(data, start, end) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + return String(Base64.getEncoder().encode(byteBuffer).array()) + } + + private fun addVarEncoded(value: Int) { + var remaining: Int = value + do { + // Grow data if needed (double size). + if (end == data.size) { + val newData = ByteArray(2 * end) + System.arraycopy(data, 0, newData, 0, end) + data = newData + } + if ((remaining and 0x7f.inv()) != 0) { + data[end++] = ((remaining and 0x7f) or 0x80).toByte() + } else { + data[end++] = remaining.toByte() + } + remaining = remaining ushr 7 + } while (remaining != 0) + } + + private fun addI(value: Int, idx: Int) { + val delta: Int = value - lastValues[idx] + addVarEncoded(zigzagEncode(delta)) + lastValues[idx] = value + } + + /** Adds an entry to the buffer. + * + * [values] length must be the same as entrySize provided in the constructor. */ + fun add(vararg values: Int) { + if (values.size != lastValues.size) { + throw IllegalArgumentException() + } + for (idx in values.indices) { + addI(values[idx], idx) + } + size++ + } + + fun toArray(): IntArray { + val values: IntBuffer = IntBuffer.allocate(lastValues.size * size) + val it = DeltaIterator() + while (it.next()) { + values.put(it.current()) + } + val next: IntArray = lastValues.copyOf(lastValues.size) + var nextIdx: Int = next.size - 1 + for (valueIdx in values.position() - 1 downTo 0) { + val value: Int = values.get(valueIdx) + values.put(valueIdx, next[nextIdx]) + next[nextIdx] -= value + nextIdx = (nextIdx + 1) % next.size + } + return values.array() + } + + private inner class DeltaIterator { + + private val buffer: ByteBuffer = ByteBuffer.wrap(data) + private val currentValues: IntArray = IntArray(lastValues.size) + private var more: Boolean = false + fun current(): IntArray { + return currentValues + } + + private fun readNext(): Int { + var v = 0 + var offset = 0 + var b: Int + do { + if (!buffer.hasRemaining()) { + more = false + return 0 + } + b = buffer.get().toInt() + v = v or ((b and 0x7f) shl offset) + offset += 7 + } while ((b and 0x80) != 0) + return zigzagDecode(v) + } + + operator fun next(): Boolean { + if (!buffer.hasRemaining()) return false + more = true + var i = 0 + while (i < currentValues.size && more) { + currentValues[i] = readNext() + i++ + } + return more + } + + init { + buffer.position(start) + buffer.limit(end) + buffer.order(ByteOrder.LITTLE_ENDIAN) + } + } + + companion object { + + private fun toLongBoundary(i: Int): Int { + return 8 * ((i + 7) / 8) + } + + private fun zigzagEncode(i: Int): Int { + return (i shr 31) xor (i shl 1) + } + + private fun zigzagDecode(i: Int): Int { + return (i ushr 1) xor -(i and 1) + } + } +} \ No newline at end of file diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/GarminPlugin.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/GarminPlugin.kt new file mode 100644 index 0000000000..0b28af0e94 --- /dev/null +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/GarminPlugin.kt @@ -0,0 +1,246 @@ +package app.aaps.plugins.sync.garmin + +import androidx.annotation.VisibleForTesting +import app.aaps.core.interfaces.db.GlucoseUnit +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.plugin.PluginBase +import app.aaps.core.interfaces.plugin.PluginDescription +import app.aaps.core.interfaces.plugin.PluginType +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.EventNewBG +import app.aaps.core.interfaces.rx.events.EventPreferenceChange +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.database.entities.GlucoseValue +import app.aaps.plugins.sync.R +import com.google.gson.JsonObject +import dagger.android.HasAndroidInjector +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.schedulers.Schedulers +import java.net.SocketAddress +import java.net.URI +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.util.* +import java.util.concurrent.locks.Condition +import java.util.concurrent.locks.ReentrantLock +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.concurrent.withLock +import kotlin.math.roundToInt + +/** Support communication with Garmin devices. + * + * This plugin supports sending glucose values to Garmin devices and receiving + * carbs, heart rate and pump disconnect events from the device. It communicates + * via HTTP on localhost or Garmin's native CIQ library. + */ +@Singleton +class GarminPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + resourceHelper: ResourceHelper, + private val loopHub: LoopHub, + private val rxBus: RxBus, + private val sp: SP, +) : PluginBase( + PluginDescription() + .mainType(PluginType.SYNC) + .pluginIcon(app.aaps.core.main.R.drawable.ic_watch) + .pluginName(R.string.garmin) + .shortName(R.string.garmin) + .description(R.string.garmin_description) + .preferencesId(R.xml.pref_garmin), + aapsLogger, resourceHelper, injector +) { + /** HTTP Server for local HTTP server communication (device app requests values) .*/ + private var server: HttpServer? = null + + private val disposable = CompositeDisposable() + + @VisibleForTesting + var clock: Clock = Clock.systemUTC() + + private val valueLock = ReentrantLock() + @VisibleForTesting + var newValue: Condition = valueLock.newCondition() + private var lastGlucoseValueTimestamp: Long? = null + private val glucoseUnitStr get() = if (loopHub.glucoseUnit == GlucoseUnit.MGDL) "mgdl" else "mmoll" + + private fun onPreferenceChange(event: EventPreferenceChange) { + aapsLogger.info(LTag.GARMIN, "preferences change ${event.changedKey}") + setupHttpServer() + } + + override fun onStart() { + super.onStart() + aapsLogger.info(LTag.GARMIN, "start") + disposable.add( + rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(Schedulers.io()) + .subscribe(::onPreferenceChange) + ) + setupHttpServer() + } + + private fun setupHttpServer() { + if (sp.getBoolean("communication_http", false)) { + val port = sp.getInt("communication_http_port", 28891) + if (server != null && server?.port == port) return + aapsLogger.info(LTag.GARMIN, "starting HTTP server on $port") + server?.close() + server = HttpServer(aapsLogger, port).apply { + registerEndpoint("/get", ::onGetBloodGlucose) + } + } else if (server != null) { + aapsLogger.info(LTag.GARMIN, "stopping HTTP server") + server?.close() + server = null + } + } + + override fun onStop() { + disposable.clear() + aapsLogger.info(LTag.GARMIN, "Stop") + server?.close() + server = null + super.onStop() + } + + /** Receive new blood glucose events. + * + * Stores new blood glucose values in lastGlucoseValue to make sure we return + * these values immediately when values are requested by Garmin device. + * Sends a message to the Garmin devices via the ciqMessenger. */ + @VisibleForTesting + fun onNewBloodGlucose(event: EventNewBG) { + val timestamp = event.glucoseValueTimestamp ?: return + aapsLogger.info(LTag.GARMIN, "onNewBloodGlucose ${Date(timestamp)}") + valueLock.withLock { + if ((lastGlucoseValueTimestamp?: 0) >= timestamp) return + lastGlucoseValueTimestamp = timestamp + newValue.signalAll() + } + } + + /** Gets the last 2+ hours of glucose values. */ + @VisibleForTesting + fun getGlucoseValues(): List { + val from = clock.instant().minus(Duration.ofHours(2).plusMinutes(9)) + return loopHub.getGlucoseValues(from, true) + } + + /** Get the last 2+ hours of glucose values and waits in case a new value should arrive soon. */ + private fun getGlucoseValues(maxWait: Duration): List { + val glucoseFrequency = Duration.ofMinutes(5) + val glucoseValues = getGlucoseValues() + val last = glucoseValues.lastOrNull() ?: return emptyList() + val delay = Duration.ofMillis(clock.millis() - last.timestamp) + return if (!maxWait.isZero + && delay > glucoseFrequency + && delay < glucoseFrequency.plusMinutes(1)) { + valueLock.withLock { + aapsLogger.debug(LTag.GARMIN, "waiting for new glucose (delay=$delay)") + newValue.awaitNanos(maxWait.toNanos()) + } + getGlucoseValues() + } else { + glucoseValues + } + } + + private fun encodedGlucose(glucoseValues: List): String { + val encodedGlucose = DeltaVarEncodedList(glucoseValues.size * 16, 2) + for (glucose: GlucoseValue in glucoseValues) { + val timeSec: Int = (glucose.timestamp / 1000).toInt() + val glucoseMgDl: Int = glucose.value.roundToInt() + encodedGlucose.add(timeSec, glucoseMgDl) + } + aapsLogger.info( + LTag.GARMIN, + "retrieved ${glucoseValues.size} last ${Date(glucoseValues.lastOrNull()?.timestamp ?: 0L)} ${encodedGlucose.size}" + ) + return encodedGlucose.encodedBase64() + } + + /** Responses to get glucose value request by the device. + * + * Also, gets the heart rate readings from the device. + */ + @VisibleForTesting + @Suppress("UNUSED_PARAMETER") + fun onGetBloodGlucose(caller: SocketAddress, uri: URI, requestBody: String?): CharSequence { + aapsLogger.info(LTag.GARMIN, "get from $caller resp , req: $uri") + receiveHeartRate(uri) + val profileName = loopHub.currentProfileName + val waitSec = getQueryParameter(uri, "wait", 0L) + val glucoseValues = getGlucoseValues(Duration.ofSeconds(waitSec)) + val jo = JsonObject() + jo.addProperty("encodedGlucose", encodedGlucose(glucoseValues)) + jo.addProperty("remainingInsulin", loopHub.insulinOnboard) + jo.addProperty("glucoseUnit", glucoseUnitStr) + loopHub.temporaryBasal.also { + if (!it.isNaN()) jo.addProperty("temporaryBasalRate", it) + } + jo.addProperty("profile", profileName.first().toString()) + jo.addProperty("connected", loopHub.isConnected) + return jo.toString().also { + aapsLogger.info(LTag.GARMIN, "get from $caller resp , req: $uri, result: $it") + } + } + + private fun getQueryParameter(uri: URI, name: String) = (uri.query ?: "") + .split("&") + .map { kv -> kv.split("=") } + .firstOrNull { kv -> kv.size == 2 && kv[0] == name }?.get(1) + + private fun getQueryParameter( + uri: URI, + @Suppress("SameParameterValue") name: String, + @Suppress("SameParameterValue") defaultValue: Boolean): Boolean { + return when (getQueryParameter(uri, name)?.lowercase()) { + "true" -> true + "false" -> false + else -> defaultValue + } + } + + private fun getQueryParameter( + uri: URI, name: String, + @Suppress("SameParameterValue") defaultValue: Long + ): Long { + val value = getQueryParameter(uri, name) + return try { + if (value.isNullOrEmpty()) defaultValue else value.toLong() + } catch (e: NumberFormatException) { + aapsLogger.error(LTag.GARMIN, "invalid $name value '$value'") + defaultValue + } + } + + @VisibleForTesting + fun receiveHeartRate(uri: URI) { + val avg: Int = getQueryParameter(uri, "hr", 0L).toInt() + val samplingStartSec: Long = getQueryParameter(uri, "hrStart", 0L) + val samplingEndSec: Long = getQueryParameter(uri, "hrEnd", 0L) + val device: String? = getQueryParameter(uri, "device") + receiveHeartRate( + Instant.ofEpochSecond(samplingStartSec), Instant.ofEpochSecond(samplingEndSec), + avg, device, getQueryParameter(uri, "test", false)) + } + + private fun receiveHeartRate( + samplingStart: Instant, samplingEnd: Instant, + avg: Int, device: String?, test: Boolean) { + aapsLogger.info(LTag.GARMIN, "average heart rate $avg BPM test=$test") + if (test) return + if (avg > 10 && samplingStart > Instant.ofEpochMilli(0L) && samplingEnd > samplingStart) { + loopHub.storeHeartRate(samplingStart, samplingEnd, avg, device) + } else { + aapsLogger.warn(LTag.GARMIN, "Skip saving invalid HR $avg $samplingStart..$samplingEnd") + } + } +} diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/HttpServer.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/HttpServer.kt new file mode 100644 index 0000000000..c349e3cb3d --- /dev/null +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/HttpServer.kt @@ -0,0 +1,271 @@ +package app.aaps.plugins.sync.garmin + +import android.os.StrictMode +import androidx.annotation.VisibleForTesting +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import java.io.BufferedOutputStream +import java.io.ByteArrayOutputStream +import java.io.Closeable +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.lang.Thread.UncaughtExceptionHandler +import java.net.HttpURLConnection +import java.net.Inet4Address +import java.net.InetSocketAddress +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketAddress +import java.net.SocketTimeoutException +import java.net.URI +import java.nio.charset.Charset +import java.nio.charset.StandardCharsets +import java.time.Duration +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.concurrent.locks.ReentrantLock +import java.util.regex.Pattern +import kotlin.concurrent.withLock + +/** Basic HTTP server to communicate with Garmin device via localhost. */ +class HttpServer internal constructor(private var aapsLogger: AAPSLogger, val port: Int) : Closeable { + + private val serverThread: Thread + private val workerExecutor: Executor = Executors.newCachedThreadPool() + private val endpoints: MutableMap CharSequence> = + ConcurrentHashMap() + private var serverSocket: ServerSocket? = null + private val readyLock = ReentrantLock() + private val readyCond = readyLock.newCondition() + + init { + serverThread = Thread { runServer() } + serverThread.name = "GarminHttpServer" + serverThread.isDaemon = true + serverThread.uncaughtExceptionHandler = UncaughtExceptionHandler { _, e -> + e.printStackTrace() + aapsLogger.error(LTag.GARMIN, "uncaught in HTTP server", e) + serverSocket?.use {} + } + serverThread.start() + } + + override fun close() { + try { + serverSocket?.close() + serverSocket = null + } catch (_: IOException) { + } + try { + serverThread.join(10_000L) + } catch (_: InterruptedException) { + } + } + + /** Wait for the server to start listing to requests. */ + fun awaitReady(wait: Duration): Boolean { + var waitNanos = wait.toNanos() + readyLock.withLock { + while (serverSocket?.isBound != true && waitNanos > 0L) { + waitNanos = readyCond.awaitNanos(waitNanos) + } + } + return serverSocket?.isBound ?: false + } + + /** Register an endpoint (path) to handle requests. */ + fun registerEndpoint(path: String, endpoint: (SocketAddress, URI, String?) -> CharSequence) { + aapsLogger.info(LTag.GARMIN, "Register: '$path'") + endpoints[path] = endpoint + } + + // @Suppress("all") + private fun respond( + @Suppress("SameParameterValue") code: Int, + body: CharSequence, + @Suppress("SameParameterValue") contentType: String, + out: OutputStream + ) { + respond(code, body.toString().toByteArray(Charset.forName("UTF8")), contentType, out) + } + + private fun respond(code: Int, out: OutputStream) { + respond(code, null as ByteArray?, null, out) + } + + private fun respond(code: Int, body: ByteArray?, contentType: String?, out: OutputStream) { + val header = StringBuilder() + header.append("HTTP/1.1 ").append(code).append(" OK\r\n") + if (body != null) { + appendHeader("Content-Length", "" + body.size, header) + } + if (contentType != null) { + appendHeader("Content-Type", contentType, header) + } + header.append("\r\n") + val bout = BufferedOutputStream(out) + bout.write(header.toString().toByteArray(StandardCharsets.US_ASCII)) + if (body != null) { + bout.write(body) + } + bout.flush() + } + + private fun handleRequest(s: Socket) { + val out = s.getOutputStream() + try { + val (uri, reqBody) = parseRequest(s.getInputStream()) + if ("favicon.ico" == uri.path) { + respond(HttpURLConnection.HTTP_NOT_FOUND, out) + return + } + val endpoint = endpoints[uri.path ?: ""] + if (endpoint == null) { + aapsLogger.error(LTag.GARMIN, "request path not found '" + uri.path + "'") + respond(HttpURLConnection.HTTP_NOT_FOUND, out) + } else { + try { + val body = endpoint(s.remoteSocketAddress, uri, reqBody) + respond(HttpURLConnection.HTTP_OK, body, "application/json", out) + } catch (e: Exception) { + aapsLogger.error(LTag.GARMIN, "endpoint " + uri.path + " failed", e) + respond(HttpURLConnection.HTTP_INTERNAL_ERROR, out) + } + } + } catch (e: SocketTimeoutException) { + // Client may just connect without sending anything. + aapsLogger.debug(LTag.GARMIN, "socket timeout: " + e.message) + return + } catch (e: IOException) { + aapsLogger.error(LTag.GARMIN, "Invalid request", e) + respond(HttpURLConnection.HTTP_BAD_REQUEST, out) + return + } + } + + private fun runServer() = try { + // Policy won't work in unit tests, so ignore NULL builder. + @Suppress("UNNECESSARY_SAFE_CALL") + val policy = StrictMode.ThreadPolicy.Builder()?.permitAll()?.build() + if (policy != null) StrictMode.setThreadPolicy(policy) + readyLock.withLock { + serverSocket = ServerSocket() + serverSocket!!.bind( + // Garmin will only connect to IP4 localhost. Therefore, we need to explicitly listen + // on that loopback interface and cannot use InetAddress.getLoopbackAddress(). That + // gives ::1 (IP6 localhost). + InetSocketAddress(Inet4Address.getByAddress(byteArrayOf(127, 0, 0, 1)), port) + ) + readyCond.signalAll() + } + aapsLogger.info(LTag.GARMIN, "accept connections on " + serverSocket!!.localSocketAddress) + while (true) { + val socket = serverSocket!!.accept() + aapsLogger.info(LTag.GARMIN, "accept " + socket.remoteSocketAddress) + workerExecutor.execute { + Thread.currentThread().name = "worker" + Thread.currentThread().id + try { + socket.use { s -> + s.soTimeout = 10_000 + handleRequest(s) + } + } catch (e: Exception) { + aapsLogger.error(LTag.GARMIN, "response failed", e) + } + } + } + } catch (e: IOException) { + aapsLogger.error("Server crashed", e) + } finally { + try { + serverSocket?.close() + serverSocket = null + } catch (e: IOException) { + aapsLogger.error(LTag.GARMIN, "Socked close failed", e) + } + } + + companion object { + + private val REQUEST_HEADER = Pattern.compile("(GET|POST) (\\S*) HTTP/1.1") + private val HEADER_LINE = Pattern.compile("([A-Za-z-]+)\\s*:\\s*(.*)") + + private fun readLine(input: InputStream, charset: Charset): String { + val buffer = ByteArrayOutputStream(input.available()) + loop@ while (true) { + when (val c = input.read()) { + '\r'.code -> {} + + -1 -> break@loop + '\n'.code -> break@loop + else -> buffer.write(c) + } + } + return String(buffer.toByteArray(), charset) + } + + @VisibleForTesting + internal fun readBody(input: InputStream, length: Int): String { + var remaining = length + val buffer = ByteArrayOutputStream(input.available()) + var c: Int = -1 + while (remaining-- > 0 && (input.read().also { c = it }) != -1) { + buffer.write(c) + } + return buffer.toString("UTF8") + } + + /** Parses a requests and returns the URI and the request body. */ + @VisibleForTesting + internal fun parseRequest(input: InputStream): Pair { + val headerLine = readLine(input, Charset.forName("ASCII")) + val p = REQUEST_HEADER.matcher(headerLine) + if (!p.matches()) { + throw IOException("invalid HTTP header '$headerLine'") + } + val post = ("POST" == p.group(1)) + var uri = URI(p.group(2)) + val headers: MutableMap = HashMap() + while (true) { + val line = readLine(input, Charset.forName("ASCII")) + if (line.isEmpty()) { + break + } + val m = HEADER_LINE.matcher(line) + if (!m.matches()) { + throw IOException("invalid header line '$line'") + } + headers[m.group(1)!!] = m.group(2) + } + var body: String? + if (post) { + val contentLength = headers["Content-Length"]?.toInt() ?: Int.MAX_VALUE + val keepAlive = ("Keep-Alive" == headers["Connection"]) + val contentType = headers["Content-Type"] + if (keepAlive && contentLength == Int.MAX_VALUE) { + throw IOException("keep-alive without content-length for $uri") + } + body = readBody(input, contentLength) + if (("application/x-www-form-urlencoded" == contentType)) { + uri = URI(uri.scheme, uri.userInfo, uri.host, uri.port, uri.path, body, null) + // uri.encodedQuery(body) + body = null + } else if ("application/json" != contentType && body.isNotBlank()) { + body = null + } + } else { + body = null + } + return Pair(uri, body?.takeUnless(String::isBlank)) + } + + private fun appendHeader(name: String, value: String, header: StringBuilder) { + header.append(name) + header.append(": ") + header.append(value) + header.append("\r\n") + } + } +} diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHub.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHub.kt new file mode 100644 index 0000000000..69f2f44a62 --- /dev/null +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHub.kt @@ -0,0 +1,41 @@ +package app.aaps.plugins.sync.garmin + +import app.aaps.core.interfaces.db.GlucoseUnit +import app.aaps.core.interfaces.profile.Profile +import app.aaps.database.entities.GlucoseValue +import java.time.Instant + +/** Abstraction from all the functionality we need from the AAPS app. */ +interface LoopHub { + + /** Returns the active insulin profile. */ + val currentProfile: Profile? + + /** Returns the name of the active insulin profile. */ + val currentProfileName: String + + /** Returns the glucose unit (mg/dl or mmol/l) as selected by the user. */ + val glucoseUnit: GlucoseUnit + + /** Returns the remaining bolus insulin on board. */ + val insulinOnboard: Double + + /** Returns true if the pump is connected. */ + val isConnected: Boolean + + /** Returns true if the current profile is set of a limited amount of time. */ + val isTemporaryProfile: Boolean + + /** Returns the factor by which the basal rate is currently raised (> 1) or lowered (< 1). */ + val temporaryBasal: Double + + /** Retrieves the glucose values starting at from. */ + fun getGlucoseValues(from: Instant, ascending: Boolean): List + + /** Stores hear rate readings that a taken and averaged of the given interval. */ + fun storeHeartRate( + samplingStart: Instant, samplingEnd: Instant, + avgHeartRate: Int, + device: String? + ) +} \ No newline at end of file diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHubImpl.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHubImpl.kt new file mode 100644 index 0000000000..e762d27b49 --- /dev/null +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/garmin/LoopHubImpl.kt @@ -0,0 +1,88 @@ +package app.aaps.plugins.sync.garmin + +import androidx.annotation.VisibleForTesting +import app.aaps.core.interfaces.aps.Loop +import app.aaps.core.interfaces.db.GlucoseUnit +import app.aaps.core.interfaces.iob.IobCobCalculator +import app.aaps.core.interfaces.profile.Profile +import app.aaps.core.interfaces.profile.ProfileFunction +import app.aaps.database.ValueWrapper +import app.aaps.database.entities.EffectiveProfileSwitch +import app.aaps.database.entities.GlucoseValue +import app.aaps.database.entities.HeartRate +import app.aaps.database.impl.AppRepository +import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction +import java.time.Clock +import java.time.Instant +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +/** + * Interface to the functionality of the looping algorithm and storage systems. + */ +class LoopHubImpl @Inject constructor( + private val iobCobCalculator: IobCobCalculator, + private val loop: Loop, + private val profileFunction: ProfileFunction, + private val repo: AppRepository, +) : LoopHub { + + @VisibleForTesting + var clock: Clock = Clock.systemUTC() + + /** Returns the active insulin profile. */ + override val currentProfile: Profile? get() = profileFunction.getProfile() + + /** Returns the name of the active insulin profile. */ + override val currentProfileName: String + get() = profileFunction.getProfileName() + + /** Returns the glucose unit (mg/dl or mmol/l) as selected by the user. */ + override val glucoseUnit: GlucoseUnit + get() = profileFunction.getProfile()?.units ?: GlucoseUnit.MGDL + + /** Returns the remaining bolus insulin on board. */ + override val insulinOnboard: Double + get() = iobCobCalculator.calculateIobFromBolus().iob + + /** Returns true if the pump is connected. */ + override val isConnected: Boolean get() = !loop.isDisconnected + + /** Returns true if the current profile is set of a limited amount of time. */ + override val isTemporaryProfile: Boolean + get() { + val resp = repo.getEffectiveProfileSwitchActiveAt(clock.millis()) + val ps: EffectiveProfileSwitch? = + (resp.blockingGet() as? ValueWrapper.Existing)?.value + return ps != null && ps.originalDuration > 0 + } + + /** Returns the factor by which the basal rate is currently raised (> 1) or lowered (< 1). */ + override val temporaryBasal: Double + get() { + val apsResult = loop.lastRun?.constraintsProcessed + return if (apsResult == null) Double.NaN else apsResult.percent / 100.0 + } + + /** Retrieves the glucose values starting at from. */ + override fun getGlucoseValues(from: Instant, ascending: Boolean): List { + return repo.compatGetBgReadingsDataFromTime(from.toEpochMilli(), ascending) + .blockingGet() + } + + /** Stores hear rate readings that a taken and averaged of the given interval. */ + override fun storeHeartRate( + samplingStart: Instant, samplingEnd: Instant, + avgHeartRate: Int, + device: String?) { + val hr = HeartRate( + timestamp = samplingStart.toEpochMilli(), + duration = samplingEnd.toEpochMilli() - samplingStart.toEpochMilli(), + dateCreated = clock.millis(), + beatsPerMinute = avgHeartRate.toDouble(), + device = device ?: "Garmin", + ) + repo.runTransaction(InsertOrUpdateHeartRateTransaction(hr)).blockingAwait() + } +} \ No newline at end of file diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/NSClientFragment.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/NSClientFragment.kt index b3b0d61e12..25a2d0eb3a 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/NSClientFragment.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/NSClientFragment.kt @@ -122,6 +122,8 @@ class NSClientFragment : DaggerFragment(), MenuProvider, PluginFragment { ID_MENU_CLEAR_LOG -> { nsClientPlugin?.listLog?.let { synchronized(it) { + val size = it.size + binding.recyclerview.adapter?.notifyItemRangeRemoved(0, size) it.clear() updateLog() } @@ -135,7 +137,7 @@ class NSClientFragment : DaggerFragment(), MenuProvider, PluginFragment { } ID_MENU_SEND_NOW -> { - nsClientPlugin?.resend("GUI") + handler.post { nsClientPlugin?.resend("GUI") } true } diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/events/EventConnectivityOptionChanged.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/events/EventConnectivityOptionChanged.kt index eabc9b4813..4240e5ce85 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/events/EventConnectivityOptionChanged.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsShared/events/EventConnectivityOptionChanged.kt @@ -2,4 +2,4 @@ package app.aaps.plugins.sync.nsShared.events import app.aaps.core.interfaces.rx.events.Event -class EventConnectivityOptionChanged(val blockingReason: String) : Event() \ No newline at end of file +class EventConnectivityOptionChanged(val blockingReason: String, val connected: Boolean) : Event() \ No newline at end of file diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/NSClientPlugin.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/NSClientPlugin.kt index 0665033ea2..4699a45bce 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/NSClientPlugin.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/NSClientPlugin.kt @@ -114,7 +114,7 @@ class NSClientPlugin @Inject constructor( } override fun onStop() { - context.applicationContext.unbindService(mConnection) + if (nsClientService != null) context.unbindService(mConnection) disposable.clear() super.onStop() } @@ -233,7 +233,7 @@ class NSClientPlugin @Inject constructor( is DataSyncSelector.PairProfileSwitch -> dataPair.value.interfaceIDs.nightscoutId is DataSyncSelector.PairEffectiveProfileSwitch -> dataPair.value.interfaceIDs.nightscoutId is DataSyncSelector.PairOfflineEvent -> dataPair.value.interfaceIDs.nightscoutId - else -> throw IllegalStateException() + else -> error("Unsupported type") } when (dataPair) { is DataSyncSelector.PairBolus -> dataPair.value.toJson(false, dateUtil) diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/ReceiverDelegate.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/ReceiverDelegate.kt index cf20d81516..cceb6490f0 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/ReceiverDelegate.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/ReceiverDelegate.kt @@ -94,7 +94,7 @@ class ReceiverDelegate @Inject constructor( if (newAllowedState != allowed) { allowed = newAllowedState if (allowed) blockingReason = "" - rxBus.send(EventConnectivityOptionChanged(blockingReason)) + rxBus.send(EventConnectivityOptionChanged(blockingReason, receiverStatusStore.isConnected)) } } diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/data/NSDeviceStatusHandler.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/data/NSDeviceStatusHandler.kt index 9488c200ad..078faf90ac 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/data/NSDeviceStatusHandler.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/data/NSDeviceStatusHandler.kt @@ -2,13 +2,18 @@ package app.aaps.plugins.sync.nsclient.data import app.aaps.annotations.OpenForTesting import app.aaps.core.interfaces.configuration.Config +import app.aaps.core.interfaces.notifications.Notification import app.aaps.core.interfaces.nsclient.ProcessedDeviceStatusData +import app.aaps.core.interfaces.resources.ResourceHelper import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil +import app.aaps.core.interfaces.utils.T import app.aaps.core.nssdk.interfaces.RunningConfiguration import app.aaps.core.nssdk.localmodel.devicestatus.NSDeviceStatus import app.aaps.core.utils.HtmlHelper import app.aaps.core.utils.JsonHelper +import app.aaps.plugins.sync.R import javax.inject.Inject import javax.inject.Singleton @@ -72,24 +77,31 @@ class NSDeviceStatusHandler @Inject constructor( private val config: Config, private val dateUtil: DateUtil, private val runningConfiguration: RunningConfiguration, - private val processedDeviceStatusData: ProcessedDeviceStatusData + private val processedDeviceStatusData: ProcessedDeviceStatusData, + private val uiInteraction: UiInteraction, + private val rh: ResourceHelper ) { fun handleNewData(deviceStatuses: Array) { var configurationDetected = false for (i in deviceStatuses.size - 1 downTo 0) { val nsDeviceStatus = deviceStatuses[i] - updatePumpData(nsDeviceStatus) - updateDeviceData(nsDeviceStatus) - updateOpenApsData(nsDeviceStatus) - updateUploaderData(nsDeviceStatus) - nsDeviceStatus.pump?.let { sp.putBoolean(app.aaps.core.utils.R.string.key_objectives_pump_status_is_available_in_ns, true) } // Objective 0 + if (config.NSCLIENT) { + updatePumpData(nsDeviceStatus) + updateDeviceData(nsDeviceStatus) + updateOpenApsData(nsDeviceStatus) + updateUploaderData(nsDeviceStatus) + } if (config.NSCLIENT && !configurationDetected) nsDeviceStatus.configuration?.let { // copy configuration of Insulin and Sensitivity from main AAPS runningConfiguration.apply(it) configurationDetected = true // pick only newest + } + if (config.APS) { + nsDeviceStatus.pump?.let { sp.putBoolean(app.aaps.core.utils.R.string.key_objectives_pump_status_is_available_in_ns, true) } // Objective 0 + } } } diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/services/NSClientService.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/services/NSClientService.kt index 7aa3e013c1..c05558ec8c 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/services/NSClientService.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclient/services/NSClientService.kt @@ -68,7 +68,8 @@ import java.net.URISyntaxException import java.util.* import javax.inject.Inject -@Suppress("SpellCheckingInspection") class NSClientService : DaggerService() { +@Suppress("SpellCheckingInspection") +class NSClientService : DaggerService() { @Inject lateinit var injector: HasAndroidInjector @Inject lateinit var aapsLogger: AAPSLogger diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3Plugin.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3Plugin.kt index ae351b9260..a9543a679c 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3Plugin.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3Plugin.kt @@ -1,8 +1,12 @@ package app.aaps.plugins.sync.nsclientV3 +import android.content.ComponentName import android.content.Context +import android.content.Intent +import android.content.ServiceConnection import android.os.Handler import android.os.HandlerThread +import android.os.IBinder import android.os.SystemClock import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceScreen @@ -17,7 +21,6 @@ import app.aaps.core.interfaces.configuration.Constants import app.aaps.core.interfaces.db.PersistenceLayer import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.logging.LTag -import app.aaps.core.interfaces.notifications.Notification import app.aaps.core.interfaces.nsclient.NSAlarm import app.aaps.core.interfaces.nsclient.StoreDataForDb import app.aaps.core.interfaces.plugin.PluginBase @@ -29,7 +32,6 @@ import app.aaps.core.interfaces.rx.AapsSchedulers import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventAppExit import app.aaps.core.interfaces.rx.events.EventDeviceStatusChange -import app.aaps.core.interfaces.rx.events.EventDismissNotification import app.aaps.core.interfaces.rx.events.EventNSClientNewLog import app.aaps.core.interfaces.rx.events.EventNewHistoryData import app.aaps.core.interfaces.rx.events.EventOfflineChange @@ -44,29 +46,21 @@ import app.aaps.core.interfaces.source.NSClientSource import app.aaps.core.interfaces.sync.DataSyncSelector import app.aaps.core.interfaces.sync.NsClient import app.aaps.core.interfaces.sync.Sync -import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.DecimalFormatter import app.aaps.core.interfaces.utils.T import app.aaps.core.interfaces.utils.fabric.FabricPrivacy import app.aaps.core.nssdk.NSAndroidClientImpl import app.aaps.core.nssdk.interfaces.NSAndroidClient -import app.aaps.core.nssdk.mapper.toNSDeviceStatus -import app.aaps.core.nssdk.mapper.toNSFood -import app.aaps.core.nssdk.mapper.toNSSgvV3 -import app.aaps.core.nssdk.mapper.toNSTreatment import app.aaps.core.nssdk.remotemodel.LastModified import app.aaps.database.ValueWrapper import app.aaps.database.entities.interfaces.TraceableDBEntry import app.aaps.plugins.sync.R -import app.aaps.plugins.sync.nsShared.NSAlarmObject import app.aaps.plugins.sync.nsShared.NSClientFragment -import app.aaps.plugins.sync.nsShared.NsIncomingDataProcessor import app.aaps.plugins.sync.nsShared.events.EventConnectivityOptionChanged import app.aaps.plugins.sync.nsShared.events.EventNSClientUpdateGuiData import app.aaps.plugins.sync.nsShared.events.EventNSClientUpdateGuiStatus import app.aaps.plugins.sync.nsclient.ReceiverDelegate -import app.aaps.plugins.sync.nsclient.data.NSDeviceStatusHandler import app.aaps.plugins.sync.nsclientV3.extensions.toNSBolus import app.aaps.plugins.sync.nsclientV3.extensions.toNSBolusWizard import app.aaps.plugins.sync.nsclientV3.extensions.toNSCarbs @@ -80,6 +74,7 @@ import app.aaps.plugins.sync.nsclientV3.extensions.toNSSvgV3 import app.aaps.plugins.sync.nsclientV3.extensions.toNSTemporaryBasal import app.aaps.plugins.sync.nsclientV3.extensions.toNSTemporaryTarget import app.aaps.plugins.sync.nsclientV3.extensions.toNSTherapyEvent +import app.aaps.plugins.sync.nsclientV3.services.NSClientV3Service import app.aaps.plugins.sync.nsclientV3.workers.DataSyncWorker import app.aaps.plugins.sync.nsclientV3.workers.LoadBgWorker import app.aaps.plugins.sync.nsclientV3.workers.LoadDeviceStatusWorker @@ -93,19 +88,11 @@ import com.google.gson.GsonBuilder import dagger.android.HasAndroidInjector import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign -import io.socket.client.Ack -import io.socket.client.IO -import io.socket.client.Socket -import io.socket.emitter.Emitter import kotlinx.serialization.json.Json -import org.json.JSONArray -import org.json.JSONObject -import java.net.URISyntaxException import java.security.InvalidParameterException import javax.inject.Inject import javax.inject.Singleton -@Suppress("SpellCheckingInspection") @OpenForTesting @Singleton class NSClientV3Plugin @Inject constructor( @@ -120,12 +107,9 @@ class NSClientV3Plugin @Inject constructor( private val receiverDelegate: ReceiverDelegate, private val config: Config, private val dateUtil: DateUtil, - private val uiInteraction: UiInteraction, private val dataSyncSelectorV3: DataSyncSelectorV3, private val persistenceLayer: PersistenceLayer, - private val nsDeviceStatusHandler: NSDeviceStatusHandler, private val nsClientSource: NSClientSource, - private val nsIncomingDataProcessor: NsIncomingDataProcessor, private val storeDataForDb: StoreDataForDb, private val decimalFormatter: DecimalFormatter ) : NsClient, Sync, PluginBase( @@ -156,28 +140,43 @@ class NSClientV3Plugin @Inject constructor( override val status get() = when { - sp.getBoolean(R.string.key_ns_paused, false) -> rh.gs(app.aaps.core.ui.R.string.paused) - isAllowed.not() -> blockingReason - sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true) && wsConnected -> "WS: " + rh.gs(app.aaps.core.interfaces.R.string.connected) - sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true) && !wsConnected -> "WS: " + rh.gs(R.string.not_connected) - lastOperationError != null -> rh.gs(app.aaps.core.ui.R.string.error) - nsAndroidClient?.lastStatus == null -> rh.gs(R.string.not_connected) - workIsRunning() -> rh.gs(R.string.working) - nsAndroidClient?.lastStatus?.apiPermissions?.isFull() == true -> rh.gs(app.aaps.core.interfaces.R.string.connected) - nsAndroidClient?.lastStatus?.apiPermissions?.isRead() == true -> rh.gs(R.string.read_only) - else -> rh.gs(app.aaps.core.ui.R.string.unknown) + sp.getBoolean(R.string.key_ns_paused, false) -> rh.gs(app.aaps.core.ui.R.string.paused) + isAllowed.not() -> blockingReason + sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true) && nsClientV3Service?.wsConnected == true -> "WS: " + rh.gs(app.aaps.core.interfaces.R.string.connected) + sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true) && nsClientV3Service?.wsConnected == false -> "WS: " + rh.gs(R.string.not_connected) + lastOperationError != null -> rh.gs(app.aaps.core.ui.R.string.error) + nsAndroidClient?.lastStatus == null -> rh.gs(R.string.not_connected) + workIsRunning() -> rh.gs(R.string.working) + nsAndroidClient?.lastStatus?.apiPermissions?.isFull() == true -> rh.gs(app.aaps.core.interfaces.R.string.connected) + nsAndroidClient?.lastStatus?.apiPermissions?.isRead() == true -> rh.gs(R.string.read_only) + else -> rh.gs(app.aaps.core.ui.R.string.unknown) } var lastOperationError: String? = null internal var nsAndroidClient: NSAndroidClient? = null + internal var nsClientV3Service: NSClientV3Service? = null - private val isAllowed get() = receiverDelegate.allowed - private val blockingReason get() = receiverDelegate.blockingReason + internal val isAllowed get() = receiverDelegate.allowed + internal val blockingReason get() = receiverDelegate.blockingReason val maxAge = T.days(100).msecs() internal var newestDataOnServer: LastModified? = null // timestamp of last modification for every collection provided by server internal var lastLoadedSrvModified = LastModified(LastModified.Collections()) // max srvLastModified timestamp of last fetched data for every collection internal var firstLoadContinueTimestamp = LastModified(LastModified.Collections()) // timestamp of last fetched data for every collection during initial load + internal var initialLoadFinished = false + + private val serviceConnection: ServiceConnection = object : ServiceConnection { + override fun onServiceDisconnected(name: ComponentName) { + aapsLogger.debug(LTag.NSCLIENT, "Service is disconnected") + nsClientV3Service = null + } + + override fun onServiceConnected(name: ComponentName, service: IBinder) { + aapsLogger.debug(LTag.NSCLIENT, "Service is connected") + val localBinder = service as NSClientV3Service.LocalBinder + nsClientV3Service = localBinder.serviceInstance + } + } override fun onStart() { super.onStart() @@ -192,13 +191,23 @@ class NSClientV3Plugin @Inject constructor( setClient("START") receiverDelegate.grabReceiversState() + disposable += rxBus + .toObservable(EventAppExit::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ if (nsClientV3Service != null) context.unbindService(serviceConnection) }, fabricPrivacy::logException) disposable += rxBus .toObservable(EventConnectivityOptionChanged::class.java) .observeOn(aapsSchedulers.io) .subscribe({ ev -> rxBus.send(EventNSClientNewLog("● CONNECTIVITY", ev.blockingReason)) - setClient("CONNECTIVITY") - if (isAllowed) executeLoop("CONNECTIVITY", forceNew = false) + assert(nsClientV3Service != null) + if (ev.connected) { + when { + isAllowed && nsClientV3Service?.storageSocket == null -> setClient("CONNECTIVITY") // socket must be created + !isAllowed && nsClientV3Service?.storageSocket != null -> shutdownWebsockets() + } + if (isAllowed) executeLoop("CONNECTIVITY", forceNew = false) + } rxBus.send(EventNSClientUpdateGuiStatus()) }, fabricPrivacy::logException) disposable += rxBus @@ -211,8 +220,10 @@ class NSClientV3Plugin @Inject constructor( ev.isChanged(rh.gs(R.string.key_ns_paused)) || ev.isChanged(rh.gs(app.aaps.core.utils.R.string.key_ns_alarms)) || ev.isChanged(rh.gs(app.aaps.core.utils.R.string.key_ns_announcements)) - ) + ) { + shutdownWebsockets() setClient("SETTING CHANGE") + } if (ev.isChanged(rh.gs(app.aaps.core.utils.R.string.key_local_profile_last_change))) executeUpload("PROFILE_CHANGE", forceNew = true) @@ -299,10 +310,7 @@ class NSClientV3Plugin @Inject constructor( override fun onStop() { handler.removeCallbacksAndMessages(null) disposable.clear() - storageSocket?.disconnect() - alarmSocket?.disconnect() - storageSocket = null - alarmSocket = null + shutdownWebsockets() super.onStop() } @@ -331,270 +339,35 @@ class NSClientV3Plugin @Inject constructor( } private fun setClient(reason: String) { - nsAndroidClient = NSAndroidClientImpl( - baseUrl = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace("https://", "").replace(Regex("/$"), ""), - accessToken = sp.getString(R.string.key_ns_client_token, ""), - context = context, - logging = true, - logger = { msg -> aapsLogger.debug(LTag.HTTP, msg) } - ) - if (wsConnected) { - storageSocket?.disconnect() - alarmSocket?.disconnect() - storageSocket = null - alarmSocket = null - } + if (nsAndroidClient == null) + nsAndroidClient = NSAndroidClientImpl( + baseUrl = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace("https://", "").replace(Regex("/$"), ""), + accessToken = sp.getString(R.string.key_ns_client_token, ""), + context = context, + logging = config.isEngineeringMode() || config.isDev(), + logger = { msg -> aapsLogger.debug(LTag.HTTP, msg) } + ) SystemClock.sleep(2000) initializeWebSockets(reason) rxBus.send(EventSWSyncStatus(status)) } - /********************** - WS code - **********************/ - private var storageSocket: Socket? = null - private var alarmSocket: Socket? = null - var wsConnected = false - internal var initialLoadFinished = false private fun initializeWebSockets(reason: String) { - if (!sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true)) return - if (sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").isEmpty()) return - val urlStorage = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace(Regex("/$"), "") + "/storage" - val urlAlarm = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace(Regex("/$"), "") + "/alarm" - if (!isAllowed) { - rxBus.send(EventNSClientNewLog("● WS", blockingReason)) - } else if (sp.getBoolean(R.string.key_ns_paused, false)) { - rxBus.send(EventNSClientNewLog("● WS", "paused")) - } else { - try { - // java io.client doesn't support multiplexing. create 2 sockets - storageSocket = IO.socket(urlStorage).also { socket -> - socket.on(Socket.EVENT_CONNECT, onConnectStorage) - socket.on(Socket.EVENT_DISCONNECT, onDisconnectStorage) - rxBus.send(EventNSClientNewLog("► WS", "do connect storage $reason")) - socket.connect() - socket.on("create", onDataCreateUpdate) - socket.on("update", onDataCreateUpdate) - socket.on("delete", onDataDelete) - } - if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_announcements, config.NSCLIENT) || - sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT) - ) - alarmSocket = IO.socket(urlAlarm).also { socket -> - socket.on(Socket.EVENT_CONNECT, onConnectAlarms) - socket.on(Socket.EVENT_DISCONNECT, onDisconnectAlarm) - rxBus.send(EventNSClientNewLog("► WS", "do connect alarm $reason")) - socket.connect() - socket.on("announcement", onAnnouncement) - socket.on("alarm", onAlarm) - socket.on("urgent_alarm", onUrgentAlarm) - socket.on("clear_alarm", onClearAlarm) - } - } catch (e: URISyntaxException) { - rxBus.send(EventNSClientNewLog("● WS", "Wrong URL syntax")) - } catch (e: RuntimeException) { - rxBus.send(EventNSClientNewLog("● WS", "RuntimeException")) + if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true)) { + context.bindService(Intent(context, NSClientV3Service::class.java), serviceConnection, Context.BIND_AUTO_CREATE) + while (nsClientV3Service == null) { + aapsLogger.debug(LTag.NSCLIENT, "Waiting for service start") + SystemClock.sleep(100) } + nsClientV3Service?.initializeWebSockets(reason) } } - private val onConnectStorage = Emitter.Listener { - val socketId = storageSocket?.id() ?: "NULL" - rxBus.send(EventNSClientNewLog("◄ WS", "connected storage ID: $socketId")) - if (storageSocket != null) { - val authMessage = JSONObject().also { - it.put("accessToken", sp.getString(R.string.key_ns_client_token, "")) - it.put("collections", JSONArray(arrayOf("devicestatus", "entries", "profile", "treatments", "foods", "settings"))) - } - rxBus.send(EventNSClientNewLog("► WS", "requesting auth for storage")) - storageSocket?.emit("subscribe", authMessage, Ack { args -> - val response = args[0] as JSONObject - wsConnected = if (response.optBoolean("success")) { - rxBus.send(EventNSClientNewLog("◄ WS", "Subscribed for: ${response.optString("collections")}")) - // during disconnection updated data is not received - // thus run non WS load to get missing data - executeLoop("WS_CONNECT", forceNew = false) - true - } else { - rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed")) - false - } - rxBus.send(EventNSClientUpdateGuiStatus()) - }) - } + private fun shutdownWebsockets() { + if (nsClientV3Service != null) context.unbindService(serviceConnection) + nsClientV3Service?.shutdownWebsockets() } - private val onConnectAlarms = Emitter.Listener { - val socket = alarmSocket - val socketId = socket?.id() ?: "NULL" - rxBus.send(EventNSClientNewLog("◄ WS", "connected alarms ID: $socketId")) - if (socket != null) { - val authMessage = JSONObject().also { - it.put("accessToken", sp.getString(R.string.key_ns_client_token, "")) - } - rxBus.send(EventNSClientNewLog("► WS", "requesting auth for alarms")) - socket.emit("subscribe", authMessage, Ack { args -> - val response = args[0] as JSONObject - wsConnected = if (response.optBoolean("success")) { - rxBus.send(EventNSClientNewLog("◄ WS", response.optString("message"))) - true - } else { - rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed")) - false - } - }) - } - } - - private val onDisconnectStorage = Emitter.Listener { args -> - aapsLogger.debug(LTag.NSCLIENT, "disconnect storage reason: ${args[0]}") - rxBus.send(EventNSClientNewLog("◄ WS", "disconnect storage event")) - wsConnected = false - initialLoadFinished = false - rxBus.send(EventNSClientUpdateGuiStatus()) - } - - private val onDisconnectAlarm = Emitter.Listener { args -> - aapsLogger.debug(LTag.NSCLIENT, "disconnect alarm reason: ${args[0]}") - rxBus.send(EventNSClientNewLog("◄ WS", "disconnect alarm event")) - } - - private val onDataCreateUpdate = Emitter.Listener { args -> - val response = args[0] as JSONObject - aapsLogger.debug(LTag.NSCLIENT, "onDataCreateUpdate: $response") - val collection = response.getString("colName") - val docJson = response.getJSONObject("doc") - val docString = response.getString("doc") - rxBus.send(EventNSClientNewLog("◄ WS CREATE/UPDATE", "$collection $docString")) - val srvModified = docJson.getLong("srvModified") - lastLoadedSrvModified.set(collection, srvModified) - storeLastLoadedSrvModified() - when (collection) { - "devicestatus" -> docString.toNSDeviceStatus().let { nsDeviceStatusHandler.handleNewData(arrayOf(it)) } - "entries" -> docString.toNSSgvV3()?.let { - nsIncomingDataProcessor.processSgvs(listOf(it)) - storeDataForDb.storeGlucoseValuesToDb() - } - - "profile" -> - nsIncomingDataProcessor.processProfile(docJson) - - "treatments" -> docString.toNSTreatment()?.let { - nsIncomingDataProcessor.processTreatments(listOf(it)) - storeDataForDb.storeTreatmentsToDb() - } - - "foods" -> docString.toNSFood()?.let { - nsIncomingDataProcessor.processFood(listOf(it)) - storeDataForDb.storeFoodsToDb() - } - - "settings" -> {} - } - } - - private val onDataDelete = Emitter.Listener { args -> - val response = args[0] as JSONObject - aapsLogger.debug(LTag.NSCLIENT, "onDataDelete: $response") - val collection = response.optString("colName") ?: return@Listener - val identifier = response.optString("identifier") ?: return@Listener - rxBus.send(EventNSClientNewLog("◄ WS DELETE", "$collection $identifier")) - if (collection == "treatments") { - storeDataForDb.deleteTreatment.add(identifier) - storeDataForDb.updateDeletedTreatmentsInDb() - } - if (collection == "entries") { - storeDataForDb.deleteGlucoseValue.add(identifier) - storeDataForDb.updateDeletedGlucoseValuesInDb() - } - } - - private val onAnnouncement = Emitter.Listener { args -> - - /* - { - "level":0, - "title":"Announcement", - "message":"test", - "plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true}, - "group":"Announcement", - "isAnnouncement":true, - "key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da" - } - */ - val data = args[0] as JSONObject - rxBus.send(EventNSClientNewLog("◄ ANNOUNCEMENT", data.optString("message"))) - aapsLogger.debug(LTag.NSCLIENT, data.toString()) - if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_announcements, config.NSCLIENT)) - uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) - } - private val onAlarm = Emitter.Listener { args -> - - /* - { - "level":1, - "title":"Warning HIGH", - "message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g", - "eventName":"high", - "plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true}, - "pushoverSound":"climb", - "debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}}, - "group":"default", - "key":"simplealarms_1" - } - */ - val data = args[0] as JSONObject - rxBus.send(EventNSClientNewLog("◄ ALARM", data.optString("message"))) - aapsLogger.debug(LTag.NSCLIENT, data.toString()) - if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT)) { - val snoozedTo = sp.getLong(rh.gs(app.aaps.core.utils.R.string.key_snoozed_to) + data.optString("level"), 0L) - if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) - uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) - } - } - - private val onUrgentAlarm = Emitter.Listener { args: Array -> - val data = args[0] as JSONObject - rxBus.send(EventNSClientNewLog("◄ URGENT ALARM", data.optString("message"))) - aapsLogger.debug(LTag.NSCLIENT, data.toString()) - if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT)) { - val snoozedTo = sp.getLong(rh.gs(app.aaps.core.utils.R.string.key_snoozed_to) + data.optString("level"), 0L) - if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) - uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) - } - } - - private val onClearAlarm = Emitter.Listener { args -> - - /* - { - "clear":true, - "title":"All Clear", - "message":"default - Urgent was ack'd", - "group":"default" - } - */ - val data = args[0] as JSONObject - rxBus.send(EventNSClientNewLog("◄ CLEARALARM", data.optString("title"))) - aapsLogger.debug(LTag.NSCLIENT, data.toString()) - rxBus.send(EventDismissNotification(Notification.NS_ALARM)) - rxBus.send(EventDismissNotification(Notification.NS_URGENT_ALARM)) - } - - override fun handleClearAlarm(originalAlarm: NSAlarm, silenceTimeInMilliseconds: Long) { - if (!isEnabled()) return - if (!sp.getBoolean(R.string.key_ns_upload, true)) { - aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped") - return - } - alarmSocket?.emit("ack", originalAlarm.level(), originalAlarm.group(), silenceTimeInMilliseconds) - rxBus.send(EventNSClientNewLog("► ALARMACK ", "${originalAlarm.level()} ${originalAlarm.group()} $silenceTimeInMilliseconds")) - } - - /********************** - WS code end - **********************/ - override fun resend(reason: String) { // If WS is enabled, download is triggered by changes in NS. Thus uploadOnly // Exception is after reset to full sync (initialLoadFinished == false), where @@ -638,6 +411,15 @@ class NSClientV3Plugin @Inject constructor( dataSyncSelectorV3.resetToNextFullSync() } + override fun handleClearAlarm(originalAlarm: NSAlarm, silenceTimeInMilliseconds: Long) { + if (!isEnabled()) return + if (!sp.getBoolean(R.string.key_ns_upload, true)) { + aapsLogger.debug(LTag.NSCLIENT, "Upload disabled. Message dropped") + return + } + nsClientV3Service?.handleClearAlarm(originalAlarm, silenceTimeInMilliseconds) + } + override suspend fun nsAdd(collection: String, dataPair: DataSyncSelector.DataPair, progress: String, profile: Profile?): Boolean = dbOperation(collection, dataPair, progress, Operation.CREATE, profile) @@ -700,6 +482,7 @@ class NSClientV3Plugin @Inject constructor( } } catch (e: Exception) { aapsLogger.error(LTag.NSCLIENT, "Upload exception", e) + return false } return true } @@ -930,7 +713,7 @@ class NSClientV3Plugin @Inject constructor( sp.putString(R.string.key_ns_client_v3_last_modified, Json.encodeToString(LastModified.serializer(), lastLoadedSrvModified)) } - private fun executeLoop(origin: String, forceNew: Boolean) { + internal fun executeLoop(origin: String, forceNew: Boolean) { if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_use_ws, true) && initialLoadFinished) return if (sp.getBoolean(R.string.key_ns_paused, false)) { rxBus.send(EventNSClientNewLog("● RUN", "paused $origin")) diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/services/NSClientV3Service.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/services/NSClientV3Service.kt new file mode 100644 index 0000000000..1cd66a9783 --- /dev/null +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/services/NSClientV3Service.kt @@ -0,0 +1,337 @@ +package app.aaps.plugins.sync.nsclientV3.services + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.os.Binder +import android.os.Handler +import android.os.HandlerThread +import android.os.IBinder +import android.os.PowerManager +import app.aaps.core.interfaces.configuration.Config +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.notifications.Notification +import app.aaps.core.interfaces.nsclient.NSAlarm +import app.aaps.core.interfaces.nsclient.StoreDataForDb +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.* +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.ui.UiInteraction +import app.aaps.core.interfaces.utils.fabric.FabricPrivacy +import app.aaps.core.nssdk.mapper.toNSDeviceStatus +import app.aaps.core.nssdk.mapper.toNSFood +import app.aaps.core.nssdk.mapper.toNSSgvV3 +import app.aaps.core.nssdk.mapper.toNSTreatment +import app.aaps.plugins.sync.R +import app.aaps.plugins.sync.nsShared.NSAlarmObject +import app.aaps.plugins.sync.nsShared.NsIncomingDataProcessor +import app.aaps.plugins.sync.nsShared.events.EventNSClientUpdateGuiStatus +import app.aaps.plugins.sync.nsclient.data.NSDeviceStatusHandler +import app.aaps.plugins.sync.nsclientV3.NSClientV3Plugin +import dagger.android.DaggerService +import dagger.android.HasAndroidInjector +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.socket.client.Ack +import io.socket.client.IO +import io.socket.client.Socket +import io.socket.emitter.Emitter +import org.json.JSONArray +import org.json.JSONObject +import java.net.URISyntaxException +import java.util.* +import javax.inject.Inject + +@Suppress("SpellCheckingInspection") +class NSClientV3Service : DaggerService() { + + @Inject lateinit var injector: HasAndroidInjector + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var rxBus: RxBus + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var sp: SP + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var nsClientV3Plugin: NSClientV3Plugin + @Inject lateinit var config: Config + @Inject lateinit var nsIncomingDataProcessor: NsIncomingDataProcessor + @Inject lateinit var storeDataForDb: StoreDataForDb + @Inject lateinit var uiInteraction: UiInteraction + @Inject lateinit var nsDeviceStatusHandler: NSDeviceStatusHandler + + private val disposable = CompositeDisposable() + + private var wakeLock: PowerManager.WakeLock? = null + private val binder: IBinder = LocalBinder() + private val handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) + + @SuppressLint("WakelockTimeout") + override fun onCreate() { + super.onCreate() + wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:NSClientService") + wakeLock?.acquire() + } + + override fun onDestroy() { + super.onDestroy() + disposable.clear() + if (wakeLock?.isHeld == true) wakeLock?.release() + } + + inner class LocalBinder : Binder() { + + val serviceInstance: NSClientV3Service + get() = this@NSClientV3Service + } + + override fun onBind(intent: Intent): IBinder = binder + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int = START_STICKY + + internal var storageSocket: Socket? = null + private var alarmSocket: Socket? = null + internal var wsConnected = false + + internal fun shutdownWebsockets() { + storageSocket?.on(Socket.EVENT_CONNECT, onConnectStorage) + storageSocket?.on(Socket.EVENT_DISCONNECT, onDisconnectStorage) + storageSocket?.on("create", onDataCreateUpdate) + storageSocket?.on("update", onDataCreateUpdate) + storageSocket?.on("delete", onDataDelete) + storageSocket?.disconnect() + alarmSocket?.on(Socket.EVENT_CONNECT, onConnectAlarms) + alarmSocket?.on(Socket.EVENT_DISCONNECT, onDisconnectAlarm) + alarmSocket?.on("announcement", onAnnouncement) + alarmSocket?.on("alarm", onAlarm) + alarmSocket?.on("urgent_alarm", onUrgentAlarm) + alarmSocket?.on("clear_alarm", onClearAlarm) + alarmSocket?.disconnect() + wsConnected = false + storageSocket = null + alarmSocket = null + } + + internal fun initializeWebSockets(reason: String) { + if (sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").isEmpty()) return + val urlStorage = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace(Regex("/$"), "") + "/storage" + val urlAlarm = sp.getString(app.aaps.core.utils.R.string.key_nsclientinternal_url, "").lowercase().replace(Regex("/$"), "") + "/alarm" + if (!nsClientV3Plugin.isAllowed) { + rxBus.send(EventNSClientNewLog("● WS", nsClientV3Plugin.blockingReason)) + } else if (sp.getBoolean(R.string.key_ns_paused, false)) { + rxBus.send(EventNSClientNewLog("● WS", "paused")) + } else { + try { + // java io.client doesn't support multiplexing. create 2 sockets + storageSocket = IO.socket(urlStorage).also { socket -> + socket.on(Socket.EVENT_CONNECT, onConnectStorage) + socket.on(Socket.EVENT_DISCONNECT, onDisconnectStorage) + rxBus.send(EventNSClientNewLog("► WS", "do connect storage $reason")) + socket.connect() + socket.on("create", onDataCreateUpdate) + socket.on("update", onDataCreateUpdate) + socket.on("delete", onDataDelete) + } + if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_announcements, config.NSCLIENT) || + sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT) + ) + alarmSocket = IO.socket(urlAlarm).also { socket -> + socket.on(Socket.EVENT_CONNECT, onConnectAlarms) + socket.on(Socket.EVENT_DISCONNECT, onDisconnectAlarm) + rxBus.send(EventNSClientNewLog("► WS", "do connect alarm $reason")) + socket.connect() + socket.on("announcement", onAnnouncement) + socket.on("alarm", onAlarm) + socket.on("urgent_alarm", onUrgentAlarm) + socket.on("clear_alarm", onClearAlarm) + } + } catch (e: URISyntaxException) { + rxBus.send(EventNSClientNewLog("● WS", "Wrong URL syntax")) + } catch (e: RuntimeException) { + rxBus.send(EventNSClientNewLog("● WS", "RuntimeException")) + } + } + } + + private val onConnectStorage = Emitter.Listener { + val socketId = storageSocket?.id() ?: "NULL" + rxBus.send(EventNSClientNewLog("◄ WS", "connected storage ID: $socketId")) + if (storageSocket != null) { + val authMessage = JSONObject().also { + it.put("accessToken", sp.getString(R.string.key_ns_client_token, "")) + it.put("collections", JSONArray(arrayOf("devicestatus", "entries", "profile", "treatments", "foods", "settings"))) + } + rxBus.send(EventNSClientNewLog("► WS", "requesting auth for storage")) + storageSocket?.emit("subscribe", authMessage, Ack { args -> + val response = args[0] as JSONObject + wsConnected = if (response.optBoolean("success")) { + rxBus.send(EventNSClientNewLog("◄ WS", "Subscribed for: ${response.optString("collections")}")) + // during disconnection updated data is not received + // thus run non WS load to get missing data + nsClientV3Plugin.executeLoop("WS_CONNECT", forceNew = false) + true + } else { + rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed")) + false + } + rxBus.send(EventNSClientUpdateGuiStatus()) + }) + } + } + + private val onConnectAlarms = Emitter.Listener { + val socket = alarmSocket + val socketId = socket?.id() ?: "NULL" + rxBus.send(EventNSClientNewLog("◄ WS", "connected alarms ID: $socketId")) + if (socket != null) { + val authMessage = JSONObject().also { + it.put("accessToken", sp.getString(R.string.key_ns_client_token, "")) + } + rxBus.send(EventNSClientNewLog("► WS", "requesting auth for alarms")) + socket.emit("subscribe", authMessage, Ack { args -> + val response = args[0] as JSONObject + if (response.optBoolean("success")) rxBus.send(EventNSClientNewLog("◄ WS", response.optString("message"))) + else rxBus.send(EventNSClientNewLog("◄ WS", "Auth failed")) + }) + } + } + + private val onDisconnectStorage = Emitter.Listener { args -> + aapsLogger.debug(LTag.NSCLIENT, "disconnect storage reason: ${args[0]}") + rxBus.send(EventNSClientNewLog("◄ WS", "disconnect storage event")) + wsConnected = false + nsClientV3Plugin.initialLoadFinished = false + rxBus.send(EventNSClientUpdateGuiStatus()) + } + + private val onDisconnectAlarm = Emitter.Listener { args -> + aapsLogger.debug(LTag.NSCLIENT, "disconnect alarm reason: ${args[0]}") + rxBus.send(EventNSClientNewLog("◄ WS", "disconnect alarm event")) + } + + private val onDataCreateUpdate = Emitter.Listener { args -> + val response = args[0] as JSONObject + aapsLogger.debug(LTag.NSCLIENT, "onDataCreateUpdate: $response") + val collection = response.getString("colName") + val docJson = response.getJSONObject("doc") + val docString = response.getString("doc") + rxBus.send(EventNSClientNewLog("◄ WS CREATE/UPDATE", "$collection $docString")) + val srvModified = docJson.getLong("srvModified") + nsClientV3Plugin.lastLoadedSrvModified.set(collection, srvModified) + nsClientV3Plugin.storeLastLoadedSrvModified() + when (collection) { + "devicestatus" -> docString.toNSDeviceStatus().let { nsDeviceStatusHandler.handleNewData(arrayOf(it)) } + "entries" -> docString.toNSSgvV3()?.let { + nsIncomingDataProcessor.processSgvs(listOf(it)) + storeDataForDb.storeGlucoseValuesToDb() + } + + "profile" -> + nsIncomingDataProcessor.processProfile(docJson) + + "treatments" -> docString.toNSTreatment()?.let { + nsIncomingDataProcessor.processTreatments(listOf(it)) + storeDataForDb.storeTreatmentsToDb() + } + + "foods" -> docString.toNSFood()?.let { + nsIncomingDataProcessor.processFood(listOf(it)) + storeDataForDb.storeFoodsToDb() + } + + "settings" -> {} + } + } + + private val onDataDelete = Emitter.Listener { args -> + val response = args[0] as JSONObject + aapsLogger.debug(LTag.NSCLIENT, "onDataDelete: $response") + val collection = response.optString("colName") ?: return@Listener + val identifier = response.optString("identifier") ?: return@Listener + rxBus.send(EventNSClientNewLog("◄ WS DELETE", "$collection $identifier")) + if (collection == "treatments") { + storeDataForDb.deleteTreatment.add(identifier) + storeDataForDb.updateDeletedTreatmentsInDb() + } + if (collection == "entries") { + storeDataForDb.deleteGlucoseValue.add(identifier) + storeDataForDb.updateDeletedGlucoseValuesInDb() + } + } + + private val onAnnouncement = Emitter.Listener { args -> + + /* + { + "level":0, + "title":"Announcement", + "message":"test", + "plugin":{"name":"treatmentnotify","label":"Treatment Notifications","pluginType":"notification","enabled":true}, + "group":"Announcement", + "isAnnouncement":true, + "key":"9ac46ad9a1dcda79dd87dae418fce0e7955c68da" + } + */ + val data = args[0] as JSONObject + rxBus.send(EventNSClientNewLog("◄ ANNOUNCEMENT", data.optString("message"))) + aapsLogger.debug(LTag.NSCLIENT, data.toString()) + if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_announcements, config.NSCLIENT)) + uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) + } + private val onAlarm = Emitter.Listener { args -> + + /* + { + "level":1, + "title":"Warning HIGH", + "message":"BG Now: 5 -0.2 → mmol\/L\nRaw BG: 4.8 mmol\/L Čistý\nBG 15m: 4.8 mmol\/L\nIOB: -0.02U\nCOB: 0g", + "eventName":"high", + "plugin":{"name":"simplealarms","label":"Simple Alarms","pluginType":"notification","enabled":true}, + "pushoverSound":"climb", + "debug":{"lastSGV":5,"thresholds":{"bgHigh":180,"bgTargetTop":75,"bgTargetBottom":72,"bgLow":70}}, + "group":"default", + "key":"simplealarms_1" + } + */ + val data = args[0] as JSONObject + rxBus.send(EventNSClientNewLog("◄ ALARM", data.optString("message"))) + aapsLogger.debug(LTag.NSCLIENT, data.toString()) + if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT)) { + val snoozedTo = sp.getLong(rh.gs(app.aaps.core.utils.R.string.key_snoozed_to) + data.optString("level"), 0L) + if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) + uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) + } + } + + private val onUrgentAlarm = Emitter.Listener { args: Array -> + val data = args[0] as JSONObject + rxBus.send(EventNSClientNewLog("◄ URGENT ALARM", data.optString("message"))) + aapsLogger.debug(LTag.NSCLIENT, data.toString()) + if (sp.getBoolean(app.aaps.core.utils.R.string.key_ns_alarms, config.NSCLIENT)) { + val snoozedTo = sp.getLong(rh.gs(app.aaps.core.utils.R.string.key_snoozed_to) + data.optString("level"), 0L) + if (snoozedTo == 0L || System.currentTimeMillis() > snoozedTo) + uiInteraction.addNotificationWithAction(injector, NSAlarmObject(data)) + } + } + + private val onClearAlarm = Emitter.Listener { args -> + + /* + { + "clear":true, + "title":"All Clear", + "message":"default - Urgent was ack'd", + "group":"default" + } + */ + val data = args[0] as JSONObject + rxBus.send(EventNSClientNewLog("◄ CLEARALARM", data.optString("title"))) + aapsLogger.debug(LTag.NSCLIENT, data.toString()) + rxBus.send(EventDismissNotification(Notification.NS_ALARM)) + rxBus.send(EventDismissNotification(Notification.NS_URGENT_ALARM)) + } + + fun handleClearAlarm(originalAlarm: NSAlarm, silenceTimeInMilliseconds: Long) { + alarmSocket?.emit("ack", originalAlarm.level(), originalAlarm.group(), silenceTimeInMilliseconds) + rxBus.send(EventNSClientNewLog("► ALARMACK ", "${originalAlarm.level()} ${originalAlarm.group()} $silenceTimeInMilliseconds")) + } +} diff --git a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/workers/DataSyncWorker.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/workers/DataSyncWorker.kt index b2d6059be5..4384fab58c 100644 --- a/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/workers/DataSyncWorker.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/nsclientV3/workers/DataSyncWorker.kt @@ -23,14 +23,14 @@ class DataSyncWorker( @Inject lateinit var nsClientV3Plugin: NSClientV3Plugin override suspend fun doWorkAndLog(): Result { - if (activePlugin.activeNsClient?.hasWritePermission == true || nsClientV3Plugin.wsConnected) { + if (activePlugin.activeNsClient?.hasWritePermission == true || nsClientV3Plugin.nsClientV3Service?.wsConnected == true) { rxBus.send(EventNSClientNewLog("► UPL", "Start")) dataSyncSelectorV3.doUpload() rxBus.send(EventNSClientNewLog("► UPL", "End")) } else { if (activePlugin.activeNsClient?.hasWritePermission == true) rxBus.send(EventNSClientNewLog("► ERROR", "No write permission")) - else if (nsClientV3Plugin.wsConnected) + else if (nsClientV3Plugin.nsClientV3Service?.wsConnected == true) rxBus.send(EventNSClientNewLog("► ERROR", "Not connected")) // refresh token nsClientV3Plugin.scheduleIrregularExecution(refreshToken = true) diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearFragment.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearFragment.kt similarity index 96% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearFragment.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearFragment.kt index acda63dfc0..9dc68e02ea 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearFragment.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearFragment.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear +package app.aaps.plugins.sync.wear import android.content.Intent import android.os.Bundle @@ -21,9 +21,9 @@ import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.fabric.FabricPrivacy import app.aaps.core.ui.toast.ToastUtils -import app.aaps.plugins.main.R -import app.aaps.plugins.main.databinding.WearFragmentBinding -import app.aaps.plugins.main.general.wear.activities.CwfInfosActivity +import app.aaps.plugins.sync.R +import app.aaps.plugins.sync.databinding.WearFragmentBinding +import app.aaps.plugins.sync.wear.activities.CwfInfosActivity import dagger.android.support.DaggerFragment import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearPlugin.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearPlugin.kt similarity index 77% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearPlugin.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearPlugin.kt index 391a731c9c..58461db8c6 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/WearPlugin.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/WearPlugin.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear +package app.aaps.plugins.sync.wear import android.content.Context import app.aaps.core.interfaces.logging.AAPSLogger @@ -11,7 +11,6 @@ import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventAutosensCalculationFinished import app.aaps.core.interfaces.rx.events.EventDismissBolusProgressIfRunning import app.aaps.core.interfaces.rx.events.EventLoopUpdateGui -import app.aaps.core.interfaces.rx.events.EventMobileDataToWear import app.aaps.core.interfaces.rx.events.EventMobileToWear import app.aaps.core.interfaces.rx.events.EventOverviewBolusProgress import app.aaps.core.interfaces.rx.events.EventPreferenceChange @@ -21,9 +20,9 @@ import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.utils.fabric.FabricPrivacy -import app.aaps.plugins.main.R -import app.aaps.plugins.main.general.wear.wearintegration.DataHandlerMobile -import app.aaps.plugins.main.general.wear.wearintegration.DataLayerListenerServiceMobileHelper +import app.aaps.plugins.sync.R +import app.aaps.plugins.sync.wear.wearintegration.DataHandlerMobile +import app.aaps.plugins.sync.wear.wearintegration.DataLayerListenerServiceMobileHelper import dagger.android.HasAndroidInjector import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -45,7 +44,7 @@ class WearPlugin @Inject constructor( ) : PluginBase( PluginDescription() - .mainType(PluginType.GENERAL) + .mainType(PluginType.SYNC) .fragmentClass(WearFragment::class.java.name) .pluginIcon(app.aaps.core.main.R.drawable.ic_watch) .pluginName(app.aaps.core.ui.R.string.wear) @@ -112,12 +111,23 @@ class WearPlugin @Inject constructor( fun checkCustomWatchfacePreferences() { savedCustomWatchface?.let { cwf -> - val cwf_authorization = sp.getBoolean(app.aaps.core.utils.R.string.key_wear_custom_watchface_autorization, false) - if (cwf_authorization != cwf.metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull()) { - // resend new customWatchface to Watch with updated authorization for preferences update - val newCwf = cwf.copy() - newCwf.metadata[CwfMetadataKey.CWF_AUTHORIZATION] = sp.getBoolean(app.aaps.core.utils.R.string.key_wear_custom_watchface_autorization, false).toString() - rxBus.send(EventMobileDataToWear(EventData.ActionSetCustomWatchface(newCwf))) + val cwfAuthorization = sp.getBoolean(app.aaps.core.utils.R.string.key_wear_custom_watchface_autorization, false) + val cwfName = sp.getString(app.aaps.core.utils.R.string.key_wear_cwf_watchface_name, "") + val authorVersion = sp.getString(app.aaps.core.utils.R.string.key_wear_cwf_author_version, "") + val fileName = sp.getString(app.aaps.core.utils.R.string.key_wear_cwf_filename, "") + var toUpdate = false + CwfData("", cwf.metadata, mutableMapOf()).also { + if (cwfAuthorization != cwf.metadata[CwfMetadataKey.CWF_AUTHORIZATION]?.toBooleanStrictOrNull()) { + it.metadata[CwfMetadataKey.CWF_AUTHORIZATION] = cwfAuthorization.toString() + toUpdate = true + } + if (cwfName == cwf.metadata[CwfMetadataKey.CWF_NAME] && authorVersion == cwf.metadata[CwfMetadataKey.CWF_AUTHOR_VERSION] && fileName != cwf.metadata[CwfMetadataKey.CWF_FILENAME]) { + it.metadata[CwfMetadataKey.CWF_FILENAME] = fileName + toUpdate = true + } + + if (toUpdate) + rxBus.send(EventMobileToWear(EventData.ActionUpdateCustomWatchface(it))) } } } diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/activities/CwfInfosActivity.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/activities/CwfInfosActivity.kt similarity index 96% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/activities/CwfInfosActivity.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/activities/CwfInfosActivity.kt index f12b2975bc..34c0776e31 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/activities/CwfInfosActivity.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/activities/CwfInfosActivity.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear.activities +package app.aaps.plugins.sync.wear.activities import android.os.Bundle import android.view.LayoutInflater @@ -24,11 +24,11 @@ import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.utils.fabric.FabricPrivacy import app.aaps.core.interfaces.versionChecker.VersionCheckerUtils import app.aaps.core.ui.activities.TranslatedDaggerAppCompatActivity -import app.aaps.plugins.main.R -import app.aaps.plugins.main.databinding.CwfInfosActivityBinding -import app.aaps.plugins.main.databinding.CwfInfosActivityPrefItemBinding -import app.aaps.plugins.main.databinding.CwfInfosActivityViewItemBinding -import app.aaps.plugins.main.general.wear.WearPlugin +import app.aaps.plugins.sync.R +import app.aaps.plugins.sync.databinding.CwfInfosActivityBinding +import app.aaps.plugins.sync.databinding.CwfInfosActivityPrefItemBinding +import app.aaps.plugins.sync.databinding.CwfInfosActivityViewItemBinding +import app.aaps.plugins.sync.wear.WearPlugin import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import org.json.JSONObject @@ -192,7 +192,7 @@ class CwfInfosActivity : TranslatedDaggerAppCompatActivity() { val visibleKeyPairs = mutableListOf>() - for (viewKey in ViewKeys.values()) { + for (viewKey in ViewKeys.entries) { try { val jsonValue = json.optJSONObject(viewKey.key) if (jsonValue != null) { diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataHandlerMobile.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt similarity index 98% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataHandlerMobile.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt index c55939ecbc..8724ca92af 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataHandlerMobile.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataHandlerMobile.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear.wearintegration +package app.aaps.plugins.sync.wear.wearintegration import android.app.NotificationManager import android.content.Context @@ -31,6 +31,7 @@ import app.aaps.core.interfaces.rx.AapsSchedulers import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventMobileToWear import app.aaps.core.interfaces.rx.events.EventWearUpdateGui +import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.ui.UiInteraction @@ -66,7 +67,7 @@ import app.aaps.database.impl.AppRepository import app.aaps.database.impl.transactions.CancelCurrentTemporaryTargetIfAnyTransaction import app.aaps.database.impl.transactions.InsertAndCancelCurrentTemporaryTargetTransaction import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction -import app.aaps.plugins.main.R +import app.aaps.plugins.sync.R import dagger.android.HasAndroidInjector import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -1266,10 +1267,19 @@ class DataHandlerMobile @Inject constructor( private fun handleGetCustomWatchface(command: EventData.ActionGetCustomWatchface) { val customWatchface = command.customWatchface - aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${command.sourceNodeId}: ${customWatchface.customWatchfaceData.json}") - rxBus.send(EventWearUpdateGui(customWatchface.customWatchfaceData, command.exportFile)) + aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${command.sourceNodeId}") + val cwfData = customWatchface.customWatchfaceData + rxBus.send(EventWearUpdateGui(cwfData, command.exportFile)) + val watchfaceName = sp.getString(app.aaps.core.utils.R.string.key_wear_cwf_watchface_name, "") + val authorVersion = sp.getString(app.aaps.core.utils.R.string.key_wear_cwf_author_version, "") + if (cwfData.metadata[CwfMetadataKey.CWF_NAME] != watchfaceName || cwfData.metadata[CwfMetadataKey.CWF_AUTHOR_VERSION] != authorVersion) { + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_watchface_name, cwfData.metadata[CwfMetadataKey.CWF_NAME] ?:"") + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_author_version, cwfData.metadata[CwfMetadataKey.CWF_AUTHOR_VERSION] ?:"") + sp.putString(app.aaps.core.utils.R.string.key_wear_cwf_filename, cwfData.metadata[CwfMetadataKey.CWF_FILENAME] ?:"") + } + if (command.exportFile) - importExportPrefs.exportCustomWatchface(customWatchface.customWatchfaceData, command.withDate) + importExportPrefs.exportCustomWatchface(cwfData, command.withDate) } } diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobile.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobile.kt similarity index 97% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobile.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobile.kt index ec3685409c..92fdea324e 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobile.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobile.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear.wearintegration +package app.aaps.plugins.sync.wear.wearintegration import android.os.Binder import android.os.Handler @@ -20,8 +20,8 @@ import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.utils.fabric.FabricPrivacy import app.aaps.database.impl.AppRepository -import app.aaps.plugins.main.R -import app.aaps.plugins.main.general.wear.WearPlugin +import app.aaps.plugins.sync.R +import app.aaps.plugins.sync.wear.WearPlugin import com.google.android.gms.tasks.Tasks import com.google.android.gms.wearable.CapabilityClient import com.google.android.gms.wearable.CapabilityInfo @@ -93,7 +93,7 @@ class DataLayerListenerServiceMobile : WearableListenerService() { disposable += rxBus .toObservable(EventMobileDataToWear::class.java) .observeOn(aapsSchedulers.io) - .subscribe { sendMessage(rxDataPath, it.payload.serializeByte()) } + .subscribe { sendMessage(rxDataPath, it.payload) } } override fun onCapabilityChanged(p0: CapabilityInfo) { diff --git a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt similarity index 97% rename from plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt rename to plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt index 68cf866205..302e022962 100644 --- a/plugins/main/src/main/kotlin/app/aaps/plugins/main/general/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt +++ b/plugins/sync/src/main/kotlin/app/aaps/plugins/sync/wear/wearintegration/DataLayerListenerServiceMobileHelper.kt @@ -1,4 +1,4 @@ -package app.aaps.plugins.main.general.wear.wearintegration +package app.aaps.plugins.sync.wear.wearintegration import android.content.ComponentName import android.content.Context diff --git a/plugins/main/src/main/res/drawable/export_custom.xml b/plugins/sync/src/main/res/drawable/export_custom.xml similarity index 100% rename from plugins/main/src/main/res/drawable/export_custom.xml rename to plugins/sync/src/main/res/drawable/export_custom.xml diff --git a/plugins/main/src/main/res/drawable/ic_cwf_infos.xml b/plugins/sync/src/main/res/drawable/ic_cwf_infos.xml similarity index 100% rename from plugins/main/src/main/res/drawable/ic_cwf_infos.xml rename to plugins/sync/src/main/res/drawable/ic_cwf_infos.xml diff --git a/plugins/main/src/main/res/drawable/load_custom.xml b/plugins/sync/src/main/res/drawable/load_custom.xml similarity index 100% rename from plugins/main/src/main/res/drawable/load_custom.xml rename to plugins/sync/src/main/res/drawable/load_custom.xml diff --git a/plugins/main/src/main/res/drawable/set_default.xml b/plugins/sync/src/main/res/drawable/set_default.xml similarity index 100% rename from plugins/main/src/main/res/drawable/set_default.xml rename to plugins/sync/src/main/res/drawable/set_default.xml diff --git a/plugins/main/src/main/res/drawable/settings_off.xml b/plugins/sync/src/main/res/drawable/settings_off.xml similarity index 100% rename from plugins/main/src/main/res/drawable/settings_off.xml rename to plugins/sync/src/main/res/drawable/settings_off.xml diff --git a/plugins/main/src/main/res/drawable/settings_on.xml b/plugins/sync/src/main/res/drawable/settings_on.xml similarity index 100% rename from plugins/main/src/main/res/drawable/settings_on.xml rename to plugins/sync/src/main/res/drawable/settings_on.xml diff --git a/plugins/main/src/main/res/layout/cwf_infos_activity.xml b/plugins/sync/src/main/res/layout/cwf_infos_activity.xml similarity index 94% rename from plugins/main/src/main/res/layout/cwf_infos_activity.xml rename to plugins/sync/src/main/res/layout/cwf_infos_activity.xml index 8648bea857..68bc98a6c1 100644 --- a/plugins/main/src/main/res/layout/cwf_infos_activity.xml +++ b/plugins/sync/src/main/res/layout/cwf_infos_activity.xml @@ -6,7 +6,7 @@ android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical" android:paddingTop="2dp" - tools:context=".general.wear.activities.CwfInfosActivity"> + tools:context="app.aaps.plugins.sync.wear.activities.CwfInfosActivity"> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + android:textSize="18sp" + tools:ignore="HardcodedText" /> + tools:context="app.aaps.plugins.sync.wear.WearFragment"> Изпратете данни за глюкоза и лечения до xDrip+. Трябва да бъде избран източник на данни „xDrip+ Sync Follower“ и приемането на данни трябва да е разрешено в Settings - Inter-app settings - Accept Glucose/Treatments Активиране на предаване на данни към xDrip+. + + + Наблюдавайте и контролирайте AndroidAPS, от вашия WearOS часовник. + (Няма активна връзка) + Статус на помпата + Статус на цикъл + Съветник:\nИнсулин: %1$.2fЕ\nВъгл: %2$dгр + Избраният съветник вече не е наличен, моля, опреснете + Съветник:%1$s\nИнсулин: %2$.2fЕ\nВъгл:%3$dгр + Временна цел непознат шаблон %1$s + Изключи текуща Временна цел? + Различни мерни единици се ползват на телефона и часовника! + Нулева цел - изключвам временна цел? + Мин КЗ е извън обхват! + Макс КЗ е извън обхват! + Временна цел \nМин: %1$s\nМакс: %2$s\nПрод: %3$s + Временна цел \nцел: %1$s\nПрод: %2$s + Временна цел \nЦел: %1$s\nЦел: %2$s\nПрод: %3$s + неуспешно - моля проверете телефона + Настройки на часовник + Контролиране от часовник + Задаване временни цели и въвеждане Лечения от часовник Android wear + Изчисления, включени в резултата на съветника: + Основни настройки + Уведомяване при SMB + Покажи SMB на часовника като стандартен болус. + Настройки на Watchface + Разрешаване на персонализиран Watchface + Разрешете заредения Watchface да променя и заключва определени настройки за екрана на часовника, за да съответстват на дизайна + Персонализиран Watchface: %1$s + Зареди Watchface + Информация за Watchface + Експорт на шаблона + Шаблон за watchface експортиран + Изпрати отново всички дани + Отвори настройките на часовника + Списък с опции заключен от Watchface + Списък с необходими настройки за Watchface + Списък с полета включени в Watchface + опитвам се да изтегля данни от помпата. + ОДД: Все още стари данни! Не може да се заредят от помпата. + Ед. + гр. + ч + Не е настроен активен профил! + Профил:\n\nОтместване във времето: %1$d\nПроцент: %2$d%%\" + Целите се прилагат само в режим АПС! + Няма хронология! + Временна цел + до + НАЧАЛНИ СТОЙНОСТИ + цел + Скорост: %1$.2fЕ/ч (%2$.2f%%) \nПродължителност %3$d мин + Не е зареден профил + Прилагане само в режим АПС! + Последният резултат не е наличен! + ЗАТВОРЕН ЦИКЪЛ + ОТВОРЕН ЦИКЪЛ + ЦИКЪЛ ИЗКЛЮЧЕН + АПС + Последно изпълнение + Последно зададено + %1$.2fЕ %1$.0f%% + Днес + тегло diff --git a/plugins/sync/src/main/res/values-ca-rES/oh_strings.xml b/plugins/sync/src/main/res/values-ca-rES/oh_strings.xml index 9dd9a684ae..3c1b819814 100644 --- a/plugins/sync/src/main/res/values-ca-rES/oh_strings.xml +++ b/plugins/sync/src/main/res/values-ca-rES/oh_strings.xml @@ -1,5 +1,7 @@ + Open Humans + OH Tancar sessió ID de membre del projecte: %1$s Enviar només si hi ha connexió WiFi diff --git a/plugins/sync/src/main/res/values-ca-rES/strings.xml b/plugins/sync/src/main/res/values-ca-rES/strings.xml index 9c2e49df09..a8c2ff86b0 100644 --- a/plugins/sync/src/main/res/values-ca-rES/strings.xml +++ b/plugins/sync/src/main/res/values-ca-rES/strings.xml @@ -79,5 +79,20 @@ Enviar tests glucèmia + Llaç desactivat + xDrip+ no instal·lat + Calibració enviada a xDrip+ + + + no funciona - si us plau comproveu al mòbil + Configuració de Wear + Control des de rellotge + Marcar objectius temporals i introduir tractaments des del rellotge. + Càlculs inclosos al resultat de l\'assistent: + Configuració general + Avís d\'SMB + Mostrar SMB al rellotge com un bolus estàndard. + Reenviar totes les dades + Obrir Configuració a Wear diff --git a/plugins/sync/src/main/res/values-cs-rCZ/strings.xml b/plugins/sync/src/main/res/values-cs-rCZ/strings.xml index 80e8fcf31a..7a7f1f4ef7 100644 --- a/plugins/sync/src/main/res/values-cs-rCZ/strings.xml +++ b/plugins/sync/src/main/res/values-cs-rCZ/strings.xml @@ -116,4 +116,74 @@ Poslat data o glykémii a ošetření do xDrip+. Musí být vybrán zdroj dat \"xDrip+ Sync Follower\" a přijímání dat musí být povoleno v Nastavení - Nastavení komunikace mezi aplikacemi - Přijímat Glykémie/Ošetření Povolit odesílání do xDrip+. + DBRO + Odesílání dat do Garmin aplikace G-Watch Wear App + + Garmin + Nastavení hodinek Garmin + + WEAR + Zobrazování stavu a řízení AAPS z hodinek s WearOS. + (Žádné hodinky nejsou připojeny) + Stav pumpy + Stav smyčky + Kalkulátor: \nInzulín: %1$.2fU\nSacharidy: %2$dg + Vybraný rychlý bolus již není k dispozici, obnovte prosím dlaždici + Rychlý bolus: %1$s\nInzulín: %2$.2fU\nSacharidy: %3$dg + Dočasný cíl neznámá předvolba: %1$s + Zrušení běžícího dočasného cíle? + Různé jednotky používané na hodinkách a telefonu! + Nulový dočasný cíl - zrušení běžícího dočasného cíle? + Minimální glykémie mimo rozsah! + Maximální glykémie mimo rozsah! + Doč. cíl:\nMin: %1$s\nMax: %2$s\nTrvání: %3$s + Doč. cíl:\nCíl: %1$s\nTrvání: %2$s + Doč. cíl:\nDůvod: %1$s\nCíl: %2$s\nTrvání: %3$s + neúspěšně - zkontrolujte mobil + Nastavení hodinek + Řízení z hodinek Wear + Nastavování dočasných cílů a vkládání ošetření na hodinkách Wear. + Kalkulace použité ve výsledku wizardu: + Základní nastavení + Oznámení při SMB + Ukazovat SMB na hodinkách jako normální bolus. + Nastavení vlastního ciferníku + Autorizace vlastního ciferníku + Autorizujte načtený vlastní ciferník, aby se změnila a uzamkla některá nastavení hodinek tak, aby vyhovovala designu ciferníku + Vlastní ciferník: %1$s + Nahrát ciferník + Informace o ciferníku + Exportovat šablonu + Vlastní šablona ciferníku exportována + Znovu poslat všechna data + Otevřít nastavení na hodinkách Wear + Seznam preferencí uzamčený hodinkami + Seznam preferencí požadovaných ciferníkem + Seznam polí zahrnutých do ciferníku + pokus o načtení dat z pumpy. + CDD: Stále stará data! Nelze načíst z pumpy. + U + g + h + Není nastaven žádný aktivní profil! + Profil:\n\nPosunutí: %1$d\nProcento: %2$d%%\" + Cíle se použijí pouze v režimu APS! + Žádné údaje o historii! + Dočasný cíl + až do + VÝCHOZÍ ROZSAH + cíl + Rychlost: %1$.2fU/h (%2$.2f%%) \nTrvání %3$d min + Není vybrán žádný profil + Použít pouze v APS módu! + Poslední výsledek není k dispozici! + UZAVŘENÁ SMYČKA + OTEVŘENÁ SMYČKA + SMYČKA ZAKÁZÁNA + APS + Poslední spuštění + Poslední provedení + %1$.2fU %1$.0f%% + Dnes + vážený diff --git a/plugins/sync/src/main/res/values-da-rDK/strings.xml b/plugins/sync/src/main/res/values-da-rDK/strings.xml index 74bbb5a74a..43dd453a48 100644 --- a/plugins/sync/src/main/res/values-da-rDK/strings.xml +++ b/plugins/sync/src/main/res/values-da-rDK/strings.xml @@ -116,4 +116,70 @@ Send glukose og behandlingsdata til xDrip+. Datakilde \"xDrip+ Sync Follower\" skal vælges og accept af data skal være aktiveret i Indstillinger - \"Inter-app settings\" - \"Accept Glucose\"/\"Treatments\" Aktivér udsendelser til xDrip+. + + + UR + Overvåg og kontrollér AndroidAPS ved hjælp af dit WearOS-ur. + (Intet ur forbundet) + Pumpestatus + Loop status + Guide:\nInsulin: %1$.2fE\nKH: %2$dg + Valgt guide er ikke længere tilgængeligt. Opdater venligst din widget + Hurtigguide: %1$s\nInsulin: %2$.2fE\nKH: %3$dg + Midlertidigmål ukendt forudindstilling: %1$s + Annullér aktuelt midlertidig mål? + Forskellige enheder brugt på ur og telefon! + 0-mål - annuller midlertidigt mål? + Min-BS udenfor området! + Max-BS udenfor området! + Midlertidigt mål:\nMin: %1$s\nMax: %2$s\nVarighed: %3$s + Midlertigt mål:\nMål: %1$s\nVarighed: %2$s + Midlertigt mål:\nGrund: %1$s\nMål: %2$s\nVarighed: %3$s + mislykkedes - tjek venligst telefonen + Indstillinger for Wear + Kontrolleringer fra Ur + Sæt midlertidige mål og indtast behandlinger fra uret. + Beregninger inkluderet i guide resultatet: + Generelle indstillinger + Giv besked ved SMB + Vis SMB på uret som en standard bolus. + Brugerdefinerede Urskiveindstillinger + Brugerdefineret Urskivegodkendelse + Godkend indlæste brugerdefinerede urskive til at ændre og låse nogle overvågningsindstillinger der passer til urskivens design + Brugerdefineret Urskive: %1$s + Indlæs Urskive + Infos Urskiver + Eksporter skabelon + Brugerdefineret urskiveskabelon eksporteret + Send alle data igen + Åbn indstillinger på ur + Liste over præferencer låst af urskiven + Liste over præferencer påkrævet af urskiven + Liste over felter inkluderet i Urskiven + forsøger at hente data fra pumpen. + TDD: Stadig gammel data! Kan ikke indlæse fra pumpen. + IE + g + t + Ingen aktiv profilskift! + Profil:\n\nTidsskift: %1$d\nProcent: %2$d%%\" + Mål gælder kun i APS-tilstand! + Ingen historisk data! + Midlertidigt mål + indtil + Standardmål + mål + Rate: %1$.2fIE/t (%2$.2f%%) \nVarighed %3$d min + Ingen profil indlæst + Gælder kun i APS-tilstand! + Sidste resultat ikke tilgængeligt! + LUKKET LOOP + ÅBEN LOOP + LOOP DEAKTIVERET + APS + Sidste brug + Sidste Aktivering + %1$.2fIE %1$.0f%% + I dag + vægtet diff --git a/plugins/sync/src/main/res/values-de-rDE/strings.xml b/plugins/sync/src/main/res/values-de-rDE/strings.xml index 1d2ef7eaf4..b2a68c3fe6 100644 --- a/plugins/sync/src/main/res/values-de-rDE/strings.xml +++ b/plugins/sync/src/main/res/values-de-rDE/strings.xml @@ -116,4 +116,70 @@ Senden Sie Glukose- und Behandlungsdaten an xDrip+. Datenquelle \"xDrip+ Sync-Follower\" muss ausgewählt werden und die Akzeptanz von Daten muss in den Einstellungen - Inter-App-Einstellungen - Glukose/Behandlung akzeptieren Aktiviere Broadcasts zu xDrip+. + + + UHR + Überwache und steuere AndroidAPS mit Deiner WearOS-Smartwatch. + (keine Uhr verbunden) + Status der Pumpe + Loop Status + Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg + Ausgewählter Quickwizard nicht mehr verfügbar, bitte aktualisiere die Kachel + QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg + Temp-Target unbekannte Voreinstellung: %1$s + Ausführung des Temp-Targets abbrechen? + Verschiedene Einheiten werden auf Uhr und Telefon verwendet! + Zero-Temp-Target - abbrechen des laufenden Temp-Targets? + Min-BG ist außerhalb des Bereichs! + Max-BG ist außerhalb des Bereichs! + Temptarget:\nMin: %1$s\nMax: %2$s\nDauer: %3$s + Temptarget:\nTarget: %1$s\nDauer: %2$s + Temp-Target:\nGrund: %1$s\nTarget: %2$s\nDauer: %3$s + Nicht erfolgreich - bitte Telefon prüfen + Wear-Einstellungen + Steuerung durch die Uhr + Setze temporäre Ziele und Behandlungen mit der Uhr + Berechnungen, die im Assistenten berücksichtigt werden: + Allgemeine Einstellungen + Bei SMB benachrichtigen + Zeige SMB auf der Uhr wie einen normalen Bolus an. + Benutzerdefinierte Watchface Einstellungen + Benutzerdefinierte Watchface Autorisierung + Autorisiere geladene benutzerdefinierte Watchface zum Ändern und Sperren einiger Watchscreen-Einstellungen für das Design der Uhr + Benutzerdefiniertes Watchface: %1$s + Watchface laden + Infos Watchface + Vorlage exportieren + Benutzerdefinierte Watchface Vorlage exportiert + Alle Daten erneut senden + Öffne Einstellungen auf der Uhr + Liste der von der Watchface gesperrten Prefs + Liste der von der Watchface angeforderten Voreinstellungen + Liste der Felder, die in der Watchface enthalten sind + versuche Daten von der Pumpe abzurufen. + TDD: verwendet alte Daten! Auslesen der Pumpe nicht möglich. + E + g + h + Kein aktiver Profilwechsel! + Profil:\n\nZeitverschiebung: %1$\nProzent: %2$d%%\" + Ziele gelten nur im APS-Modus! + Keine Verlaufsdaten! + Temporäres Ziel + bis + STANDARD-BEREICH + Ziel + Rate: %1$.2fIE/h (%2$.2f%%) \nDauer %3$d min + Kein Profil geladen + Nur im APS-Modus anwendbar! + Letztes Ergebnis ist nicht verfügbar! + CLOSED LOOP + OPEN LOOP + LOOP DEAKTIVIERT + APS + Letzte Ausführung + Letzte Abgabe + %1$.2fE %1$.0f%% + Heute + gewichtet diff --git a/plugins/sync/src/main/res/values-el-rGR/strings.xml b/plugins/sync/src/main/res/values-el-rGR/strings.xml index cc9f350243..75d5f24873 100644 --- a/plugins/sync/src/main/res/values-el-rGR/strings.xml +++ b/plugins/sync/src/main/res/values-el-rGR/strings.xml @@ -116,4 +116,69 @@ Αποστολή δεδομένων γλυκόζης και θεραπειών στο xDrip +. Η πηγή δεδομένων \"συγχρονισμός xDrip+ Ακόλουθος\" πρέπει να επιλεγεί και η αποδοχή δεδομένων πρέπει να ενεργοποιηθεί στις Ρυθμίσεις - Ρυθμίσεις Inter-app - Αποδοχή Γλυκόζης/Θεραπειών Ενεργοποίηση εκπομπών στο xDrip+. + + + Παρακολούθηση και χειρισμός του AAPS χρησιμοποιώντας το ρολόι WearOS. + (Χωρίς Σύνδεση Ρολογιού) + Κατάσταση Αντλίας + Κατάσταση κυκλώματος + Υπολογισμός. Οδηγός:\nΙνσουλίνη: %1$.2fU\nΥδατάνθρακες: %2$dg + Ο επιλεγμένος οδηγός δεν είναι πλέον διαθέσιμος, παρακαλώ ανανεώστε το πλακίδιο σας + Γρήγορος οδηγός: %1$s\nΙνσουλίνη: %2$.2fU\nΥδατάνθρακες: %3$dg + Άγνωστη προεπιλογή προσωρινού στόχου: %1$s + Ακύρωση εκτέλεσης προσωρινών στόχων; + Χρησιμοποιούνται διαφορετικές μονάδες στο ρολόι και το τηλέφωνο! + Μηδενικός-Προσωρινός-Στόχος - ακύρωση εκτέλεσης Πρισωρινών-Στόχων; + Ελάσιχτο-BG εκτός εύρους! + Μέγιστο-BG εκτός εύρους! + Προσωρινός στόχος:\nΕλάχιστο: %1$s\nΜέγιστο: %2$s\nΔιάρκεια: %3$s + Προσωρινός στόχος:\nΣτόχος: %1$s\nΔιάρκεια: %2$s + Προσωρινός στόχος:\nΑιτία: %1$s\nΣτόχος: %2$s\nΔιάρκεια: %3$s + αποτυχία - ελέγξτε τηλέφωνο + Ρυθμίσεις Wear + Έλεγχος από ρολόι + Ρυθμίστε Στόχους-Προσ Ρυθμού και βάλτε Θεραπείες από το ρολόι. + Υπολογισμοί που περιλαμβάνονται στο αποτέλεσμα του γρήγορου οδηγού: + Γενικές Ρυθμίσεις + Ειδοποίηση στο SMB + Εμφάνιση SMB στο ρολόι όπως ένα τυπικό bolus. + Προσαρμοσμένες Ρυθμίσεις Watchface + Προσαρμοσμένη Εξουσιοδότηση Watchface + Εξουσιοδοτήστε το φορτωμένο προσαρμοσμένο ρολόι για να αλλάξετε και να κλειδώσετε ορισμένες ρυθμίσεις οθόνης ρολογιού για να ταιριάζει στο σχεδιασμό watchface + Προσαρμοσμένο Watchface: %1$s + Φόρτωση Watchface + Πληροφορίες Watchface + Εξαγωγή Προτύπου + Εξήχθη προσαρμοσμένο πρότυπο watchface + Ξαναστείλτε όλα τα Δεδομένα + Ρυθμίσεις στο Wear + Λίστα προτιμήσεων που κλειδώθηκαν από το Watchface + Λίστα προτιμήσεων που απαιτούνται για το Watchface + Λίστα πεδίων που περιλαμβάνονται στο Watchface + προσπάθεια λήψης δεδομένων από την αντλία. + TDD: Ακόμα είναι παλιά τα δεδομένα! Δεν μπορεί να φορτωθεί από την αντλία. + U + g + ω + Μη ενεργή αλλαγή προφίλ! + Προφίλ:\n\nΧρονική μετατόπιση: %1$d\nΠοσοστό: %2$d%%\" + Οι στόχοι ισχύουν μόνο στη λειτουργία APS! + Δεν υπάρχουν ιστορικά δεδομένα! + Προσωρινός Στόχος + μέχρι + ΠΡΟΕΠΙΛΕΓΜΕΝΟ ΕΥΡΟΣ + στόχος + Τιμή: %1$.2fU/h (%2$.2f%%) \nΔιάρκεια %3$d λεπτά + Δεν φορτώθηκε προφίλ + Ισχύει μόνο σε λειτουργία APS! + Τελευταίο αποτέλεσμα μη διαθέσιμο! + ΚΛΕΙΣΤΟ ΚΥΚΛΩΜΑ + ΑΝΟΙΚΤΟ ΚΥΚΛΩΜΑ + ΚΥΚΛΩΜΑ ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΟ + APS + Τελευταία εκτέλεση + Τελευταία Ενέργεια + %1$.2fU %1$.0f%% + Σήμερα + σταθμισμένο diff --git a/plugins/sync/src/main/res/values-es-rES/strings.xml b/plugins/sync/src/main/res/values-es-rES/strings.xml index 4b71a41ad5..8f53774a5f 100644 --- a/plugins/sync/src/main/res/values-es-rES/strings.xml +++ b/plugins/sync/src/main/res/values-es-rES/strings.xml @@ -116,4 +116,73 @@ Enviar datos de glucosa y tratamientos a xDrip+. La fuente de datos \"xDrip+ Sync Follower\" debe estar seleccionada y la aceptación de datos debe estar activada en Ajustes - Ajustes entre aplicaciones - Aceptar glucosa/tratamientos Activar las transmisiones a xDrip+ + + Garmin + Conexión al dispositivo Garmin (Fénix, Edge, …) + Ajustes de Garmin + + RELOJ + Supervisar y controlar AAPS usando un reloj WearOS + (Ningún reloj conectado) + Estado de la bomba de insulina + Estado del bucle + Calc. Asistente:\nInsulina: %1$.2fU\nCarbohidratos: %2$dg + El asistente rápido seleccionado ya no está disponible, por favor actualice su tarjeta + Asistente Rápido: %1$s\nInsulina: %2$.2fU\nCarbohidratos: %3$dg + Objetivo Temporal preestablecido desconocido: %1$s + ¿Cancelar la ejecución del objetivo temporal? + ¡Diferentes unidades usadas en el reloj y en el teléfono! + Objetivo Temporal Zero - ¿Cancelar el Objetivo Temporal en ejecución? + ¡Glucosa mínima fuera de rango! + ¡Glucosa máxima fuera de rango! + Objetivo temporal:\nMin: %1$s\nMax: %2$s\nDuración: %3$s + Objetivo temporal:\nObjetivo: %1$s\nDuración: %2$s + ObjetivoTemporal:\nRazón: %1$s\nObjetivo: %2$s\nDuración: %3$s + sin efecto - por favor verificar en teléfono + Ajustes del reloj + Control desde el reloj + Establece objetivos temporales (OT) y añade tratamientos desde el reloj + Cálculos incluidos en el resultado del asistente: + Configuración general + Notificar los SMB + Mostrar los SMB en el reloj como un bolo estándar + Configuración personalizada de esferas + Autorización de esferas personalizadas + Autorizar a la esfera personalizada a cambiar y bloquear algunos ajustes de la pantalla del reloj, para que se adapten al diseño de la esfera + Esfera personalizada: %1$s + Cargar esfera + Información de esferas + Exportar plantilla + Plantilla de esfera personalizada exportada + Reenviar todos los datos + Abrir ajustes en el reloj + Lista de preferencias bloqueadas por la esfera + Lista de preferencias necesarias para la esfera + Lista de campos incluidos en la esfera + tratando de obtener datos de la bomba. + TDD: ¡Siguen siendo datos antiguos! No se puede cargar desde la bomba + U + g + h + Sin cambio de perfil activo + Perfil:\n\nAjuste de tiempo: %1$d\nPorcentaje: %2$d%%\" + ¡Los objetivos sólo se aplican en modo APS! + No hay datos históricos + Objetivo temporal + hasta + RANGO POR DEFECTO + objetivo + Tasa: %1$.2fU/h (%2$.2f%%) \nDuración %3$d min + Ningún perfil cargado + Sólo se aplica en modo APS + ¡Último resultado no disponible! + BUCLE CERRADO + BUCLE ABIERTO + BUCLE DESACTIVADO + APS + Última ejecución + Último acto + %1$.2fU %1$.0f%% + Hoy + ponderado diff --git a/plugins/sync/src/main/res/values-fr-rFR/strings.xml b/plugins/sync/src/main/res/values-fr-rFR/strings.xml index 4079a2c51f..5c6bed1f5f 100644 --- a/plugins/sync/src/main/res/values-fr-rFR/strings.xml +++ b/plugins/sync/src/main/res/values-fr-rFR/strings.xml @@ -116,4 +116,70 @@ Envoyer les glycémies et les traitements à xDrip+. La source de données \"xDrip+ Sync Follower\" doit être sélectionnée et l\'acceptation des données doit être activée dans Paramètres - Paramètres Inter-app - Accepter Glycémies/Traitements Activer les diffusions vers xDrip+. + + + WEAR + Surveillez et contrôlez AAPS en utilisant votre montre WearOS. + (Pas de montre connectée) + État de la pompe + Statut du la boucle + Calc. Assistant :\nInsuline : %1$.2fU\nGlucides : %2$dg + L\'assistant rapide sélectionné n\'est plus disponible, veuillez actualiser l\'écran + Assistant : %1$s\nInsuline : %2$.2fU\nGlucides : %3$dg + Préréglage inconnu de la cible temp. : %1$s + Annuler l\'exécution des cibles Temp? + Différentes unités utilisées sur la montre et le téléphone! + Pas de Cible Temp - annuler la cible temporaire en cours? + Gly mini hors limite! + Gly maxi hors limite! + Cible temp.:\nMin: %1$s\nMax : %2$s\nDurée : %3$s + Cible temp.:\nCible: %1$s\nDurée: %2$s + Cible temp.:\nRaison: %1$s\nCible : %2$s\nDurée : %3$s + échec - veuillez vérifier le téléphone + Paramètres Wear + Commandes depuis la montre + Définir les Cibles Temp et entrer les Traitements depuis la montre. + Calculs inclus dans le résultat de l’Assistant : + Paramètres généraux + Notifier en cas de SMB + Afficher SMB sur la montre comme un bolus standard. + Paramètres du Cadran Personnalisé + Autorisation du Cadran Personnalisé + Autoriser le cadran perso chargé à modifier et bloquer certains paramètres d\'affichage de la montre pour un affichage correct du cadran + Cadran personnalisé : %1$s + Charger le cadran + Infos Cadran + Exporter le modèle + Modèle du cadran personnalisé exporté + Renvoyer toutes les données + Afficher les Paramètres sur la Montre + Liste des préférences verrouillées par le cadran + Liste des préférences requises pour le cadran + Liste des champs inclus dans le cadran + tentative de récupération des données de la pompe. + DTQ: Données encore anciennes ! Impossible de charger depuis la pompe. + U + g + h + Aucun profil actif! + Profil :\n\nDécalage: %1$d\nPourcentage: %2$d%%\" + Les cibles ne s\'appliquent qu\'en mode APS! + Aucune donnée d\'historique! + Cibles Temp + jusqu\'à + PLAGE PAR DEFAUT + cible + Taux: %1$.2fU/h (%2$.2f%%) \nDurée: %3$d min + Aucun profil séléctionné + S\'applique uniquement en mode APS! + Dernier résultat non disponible! + BOUCLE FERMÉE + BOUCLE OUVERTE + BOUCLE DÉSACTIVÉE + APS + Dernière exécution + Dernière injection + %1$.2fU %1$.0f%% + Aujourd’hui + pondération diff --git a/plugins/sync/src/main/res/values-hr-rHR/oh_strings.xml b/plugins/sync/src/main/res/values-hr-rHR/oh_strings.xml index d6723a8312..7181571f22 100644 --- a/plugins/sync/src/main/res/values-hr-rHR/oh_strings.xml +++ b/plugins/sync/src/main/res/values-hr-rHR/oh_strings.xml @@ -25,6 +25,7 @@ Podaci za otklanjanje pogrešaka algoritma Podaci NISU preneseni Lozinke + Nightscout URL Tajni API Nightscouta Slobodna tekstualna polja Razumijem i slažem se. diff --git a/plugins/sync/src/main/res/values-hr-rHR/strings.xml b/plugins/sync/src/main/res/values-hr-rHR/strings.xml index 134bca51a8..34942d1f73 100644 --- a/plugins/sync/src/main/res/values-hr-rHR/strings.xml +++ b/plugins/sync/src/main/res/values-hr-rHR/strings.xml @@ -58,5 +58,28 @@ Pošalji sada + Prikaži detaljan IOB + Podijelite IOB na bolus i bazalni IOB na brojčaniku + Prikaži BGI + Dodajte BGI u redak statusa + Petlja onemogućena + xDrip+ nije instaliran + Kalibracija poslana na xDrip+ + + + Pratite i kontrolirajte AAPS koristeći svoj WearOS sat. + (Sat nije povezan) + Loop status + Trenutni cilj:\nRazlog: %1$s\nCilj: %2$s\nTrajanje: %3$s + Ovlastite učitani prilagođeni brojčanik za promjenu i zaključavanje nekih postavki prikaza sata kako bi odgovarale dizajnu brojača + Info Watchface + Popis postavki koje je zaključao Watchface + Popis postavki potrebnih za Watchface + Popis polja uključenih u Watchface + J + h + Stopa: %1$.2fU/h (%2$.2f%%) \nTrajanje %3$d min + %1$.2fU %1$.0f%% + Danas diff --git a/plugins/sync/src/main/res/values-hu-rHU/strings.xml b/plugins/sync/src/main/res/values-hu-rHU/strings.xml index 23a2908959..484e01729a 100644 --- a/plugins/sync/src/main/res/values-hu-rHU/strings.xml +++ b/plugins/sync/src/main/res/values-hu-rHU/strings.xml @@ -20,4 +20,23 @@ + + + Pumpa állapot + Wear beállítások + Általános beállítások + E + g + ó + A célértékek csak APS módra vonatkoznak! + Nincs előzményadat! + Átmeneti célérték + ALAPÉRTELMEZETT TARTOMÁNY + célérték + Ráta: %1$.2fU/ó (%2$.2f%%) \nIdőtartam %3$d perc + APS + Legutóbbi futás + %1$.2fE %1$.0f%% + Ma + súlyozott diff --git a/plugins/sync/src/main/res/values-it-rIT/strings.xml b/plugins/sync/src/main/res/values-it-rIT/strings.xml index 3623755394..1021520da1 100644 --- a/plugins/sync/src/main/res/values-it-rIT/strings.xml +++ b/plugins/sync/src/main/res/values-it-rIT/strings.xml @@ -116,4 +116,70 @@ Invia dati glicemia e trattamenti a xDrip+. La sorgente dati \"xDrip+ Sync Follower\" deve essere selezionata e l\'accettazione dei dati deve essere abilitata in: Settings - Inter-app settings - Accept Glucose/Treatments Abilita trasmissioni a xDrip+. + + + SMWA + Monitora e controlla AAPS usando il tuo smartwatch WearOS. + (Nessuno smartwatch connesso) + Stato micro + Stato loop + Calc. Wizard:\nInsulina: %1$.2fU\nCHO: %2$dg + Il calcolo rapido selezionato non è più disponibile, aggiorna il riquadro + QuickWizard: %1$s\nInsulina: %2$.2fU\nCHO: %3$dg + Preset sconosciuto target temporaneo: %1$s + Cancellare i target temporanei in esecuzione? + Differenti unità usate su smartwatch e telefono! + Nessuno target temporaneo - cancellare i target temporanei in esecuzione? + Min-BG fuori range! + Max-BG fuori range! + Temptarget:\nMin: %1$s\nMax: %2$s\nDurata: %3$s + Temptarget:\nTarget: %1$s\nDurata: %2$s + Temptarget:\nMotivo: %1$s\nTarget: %2$s\nDurata: %3$s + non riuscito - controlla il telefono + Impostazioni smartwatch + Controlli da smartwatch + Imposta Temp-Target e inserisci trattamenti dallo smartwatch. + Calcoli inclusi nel risultato del Calcolatore: + Impostazioni generali + Notifica SMB + Mostra SMB sullo smartwatch come un bolo standard. + Impostazioni watchface personalizzata + Autorizzazione watchface personalizzata + Autorizza la watchface personalizzata caricata a cambiare e bloccare alcune delle impostazioni di visualizzazione dell\'orologio per adattarsi al design della watchface + Watchface personalizzata: %1$s + Carica watchface + Informazioni watchface + Esporta template + Esportato template personalizzato di Watchface + Invia di nuovo tutti i dati + Apri impostazioni sullo smartwatch + Elenco impostazioni bloccate dalla watchface + Elenco impostazioni richieste per la watchface + Elenco dei campi inclusi nella watchface + tentativo di recupero dati dal micro. + TDD: Dati ancora vecchi! Non è possibile caricare dal micro. + U + g + h + Nessun cambio profilo attivo! + Profilo:\n\nTimeshift: %1$d\nPercentuale: %2$d%%\" + I target si applicano solo in modalità APS! + Nessun dato storico! + Target Temporaneo + fino a + RANGE PREDEFINITO + target + Velocità: %1$.2fU/h (%2$.2f%%) \nDurata %3$d min + Nessun profilo caricato + Si applica solo in modalità APS! + Ultimo risultato non disponibile! + LOOP CHIUSO + LOOP APERTO + LOOP DISABILITATO + APS + Ultima esecuzione + Ultima attivazione + %1$.2fU %1$.0f%% + Oggi + ponderato diff --git a/plugins/sync/src/main/res/values-iw-rIL/strings.xml b/plugins/sync/src/main/res/values-iw-rIL/strings.xml index 71852d9346..a164f68308 100644 --- a/plugins/sync/src/main/res/values-iw-rIL/strings.xml +++ b/plugins/sync/src/main/res/values-iw-rIL/strings.xml @@ -116,4 +116,70 @@ שולח נתוני גלוקוז וטיפולים ל-xDrip+. יש לבחור את מקור הנתונים \"xDrip+ Sync Follower\" וקבלת נתונים חייבת להיות מופעלת בהגדרות > הגדרות לשיתוף פעולה בין אפליקציות > קבל גלוקוז\\טיפולים אפשר שידור ל-xDrip+ + + Garmin + חיבור למכשיר Garmin + הגדרות Garmin + + ניטור ושליטה ב-AndroidAPS באמצעות שעון WearOS. + (השעון לא מחובר) + סטטוס המשאבה + סטטוס הלולאה + מחשבון: %1$s\n אינס\': %2$.2f יח\'\nפחמ\': %3$d גר\' + האשף המהיר שנבחר אינו זמין, נא לרענן את האריח + אשף מהיר: %1$s\n אינס\': %2$.2f יח\'\nפחמ\': %3$d גר\' + תצורה לא ידועה של מטרה זמנית: %1$s + מבטל ערך מטרה זמני נוכחי + יחידות המידה שונות בין הטלפון והשעון! + Zero-Temp-Target - בוטל, הפעלת ערכי מטרה זמניים? + ערך הסוכר המינימלי מחוץ לטווח! + ערך הסוכר המקסימלי מחוץ לטווח! + ע\' מטרה זמני:\nמינ\': %1$s\nמקס\': %2$s\nמשך: %3$s + ע\' מטרה זמני:\nמטרה: %1$s\n משך: %2$s + ע\' מטרה זמני:\nסיבה: %1$s\nמטרה: %2$s\nמשך: %3$s + נכשל - נא לבדוק את הטלפון + הגדרות Wear + שליטה מהשעון + הגדירו ערכי מטרה זמניים וציינו טיפולים מהשעון. + חישובים הכלולים בתוצאת האשף: + הגדרות כלליות + דיווח על SMB + הצג SMB על השעון כמו בולוס סטנדרטי. + הגדרות פני שעון מותאמים אישית + אישור פני שעון מותאמים אישית + פני שעון מותאמים אישית: %1$s + טעינת פני שעון + פני שעון מידע + ייצוא תבנית + תבנית פני השעון יוצאה + שלח מחדש את כל הנתונים + פתיחת הגדרות Wear + רשימת העדפות ננעלה ע\"י פני השעון + רשימת ההעדפות הנדרשת ע\"י פני השעון + רשימת שדות הכלולים בפני השעון + מנסה לטעון נתונים מהמשאבה. + סטטיסטיקות (TDD): נתונים ישנים! לא ניתן לטעון מהמשאבה. + יח\' + גר\' + ש\' + לא הופעלה החלפת פרופיל! + ערכי מטרה חלים רק במצב APS! + אין נתוני עבר! + ערכי מטרה זמניים + עד + טווח ברירת מחדל + מטרה + מינון: %1$.2f יח\'\\שעה (%2$.2f%%) \nמשך: %3$d דק\' + לא נטען פרופיל + חל רק במצב APS! + התוצאה האחרונה לא זמינה! + לולאה סגורה + לולאה פתוחה + לולאה מושבתת + APS + ההפעלה האחרונה + נקבעו לאחרונה + %1$.2fיח\' %1$.0f% + היום + משוקלל diff --git a/plugins/sync/src/main/res/values-ko-rKR/strings.xml b/plugins/sync/src/main/res/values-ko-rKR/strings.xml index 4da33b4b97..d24851e364 100644 --- a/plugins/sync/src/main/res/values-ko-rKR/strings.xml +++ b/plugins/sync/src/main/res/values-ko-rKR/strings.xml @@ -116,4 +116,69 @@ xDrip+로 글루코스 및 치료 데이터 전송하기. 데이터 소스 \"xDrip+ 동기화 팔로워\"를 선택하야 하며, 설정- 앱 간 설정 - 글루코스/치료제 수락에서 데이터 수락을 활성화해야 합니다 xDrip+에 대한 방송 활성화하기 + + + WearOS 시계를 사용하여 AAPS를 모니터링하고 제어합니다. + (열결된 워치가 없습니다) + 펌프 상태 + Loop 상태 + 계산. 마법사:\n인슐린:%1$.2fU\n탄수화물: %2$dg + 선택한 빠른 마법사를 더 이상 사용할 수 없습니다, 타일을 새로고침 하십시오 + 빠른 마법사: %1$s\n인슐린: %2$.2fU\n탄수화물: %3$dg + 임시 목표 알 수 없는 사전 설정: %1$s + 임시-목표 실행을 취소하시겠습니까? + 워치와 폰과 다른 단위입니다! + 제로-임시-목표- 임시-목표 실행을 취소하시겠습니까? + 최소-BG 범위가 넘었습니다! + 최대-BG 범위가 넘었습니다! + 임시 목표:\n최소: %1$s\n최대: %2$s\n기간: %3$s + 임시 목표:\n목표: %1$s\n기간: %2$s + 임시 목표:\n이유: %1$s\n목표: %2$s\n기간: %3$s + 성공하지 못했습니다. 폰을 확인하세요 + 워치 설정 + 워치로 제어하기 + 임시목표와 관리입력을 워치로 설정합니다. + 마법사 결과에 사용 된 계산: + 일반 설정 + SMB 알림 + 일반 Bolus처럼 워치에 SMB 표시 + 사용자 지정- 워치 페이스 설정 + 사용자 지정 워치 페이스 승인 + 로드된 사용자 지정 워치 페이스를 승인하여 워치 페이스 디자인에 맞게 일부 시계 화면의 설정을 변경하고 잠급니다 + 사용자 지정 워치 페이스: %1$s + 워치 페이스 로딩하기 + 워치 페이스 정보 + 템플릿 내보내기 + 사용자 지정 워치 페이스 템플릿 내보내기 + 모든 데이터 다시 보내기 + 워치에서 설정 열기 + 워치 페이스에 의해 잠긴 선호 목록 + 워치 페이스에 필요한 선호 목록 + 워치 페이스에 포함된 필드 목록 + 펌프에서 데이터를 가져오려고 합니다. + TDD: 여전히 오래된 데이터입니다! 펌프에서 로드할 수 없습니다. + U + g + h + 활성화된 프로파일 스위치가 없습니다! + 프로파일:\n\n시간 변화: %1$d\n백분율: %2$d%%\" + 목표는 APS 모드에서만 적용 가능합니다! + 기록이 없습니다! + 임시 목표 + 까지 + 기본 범위 + 목표 + 비율: %1$.2fU/h (%2$.2f%%) \n기간%3$d 분 + 프로파일이 로딩되지 않았습니다 + APS 모드에서만 이용 가능합니다! + 지난 결과를 이용할 수 없습니다! + CLOSED LOOP + OPEN LOOP + LOOP 비활성화 + APS + 마지막 실행 + 마지막 실행 + %1$.2fU %1$.0f%% + 오늘 + 몸무게 diff --git a/plugins/sync/src/main/res/values-lt-rLT/strings.xml b/plugins/sync/src/main/res/values-lt-rLT/strings.xml index 510c7252f4..c744786e4a 100644 --- a/plugins/sync/src/main/res/values-lt-rLT/strings.xml +++ b/plugins/sync/src/main/res/values-lt-rLT/strings.xml @@ -117,4 +117,69 @@ Įjungti duomenų perdavimą į xDrip+. Data Broadcaster + + + Stebėti ir kontroliuoti AndroidAPS naudojant WearOS laikrodį. + (nėra prijungto laikrodžio) + Pompos statusas + Ciklo statusas + Wizard asistento skaičiuotuvas:\nInsulinas: %1$.2fU\nAngliavandeniai: %2$dg + Pasirinktas greitasis patarėjas nebepasiekiamas, atnaujinkite valdiklį + QuickWizard asistentas: %1$s\nInsulinas: %2$.2fU\nAngliavandeniai: %3$dg + Nežinomas laikino tikslo ruošinys: %1$s + Atšaukti laikinus tikslus? + Laikrodyje ir telefone naudojami skirtingi vienetai! + Zero-Temp-Target - atšaukti laikinus tikslus? + Min. cukraus kiekis nepatenka į diapozoną! + Max. cukraus kiekis nepatenka į diapozoną! + Laikinas tikslas:\nMin: %1$s\nMax: %2$s\nLaikotarpis: %3$s + Laikinas tikslas:\nTikslas: %1$s\nLaikotarpis: %2$s + LT:\npriežastis: %1$s\ntikslas: %2$s\ntrukmė: %3$s + Bandymas nesėkmingas - pasitikrinkite telefoną + Išmaniojo laikrodžio nustatymai + Laikrodžio valdikliai + Nustatyti Laikinus Tikslus ir įvesti terapinius įrašus iš laikrodžio. + Skaičiavimai, įtraukti į Patarėjo rezultatą: + Bendrieji nustatymai + Pranešti apie SMB + Rodyti SMB laikrodyje kaip standartinį bolusą. + Pasirinktiniai laikrodžio ekrano nustatymai + Pasirinktinė laikrodžio ekrano autorizacija + Leisti įkeltam pasirinktiniam ekranui keisti ir užrakinti kai kuriuos laikrodžio nustatymus, kad jie atitiktų ekrano dizainą + Pasirinktinis ekranas: %1$s + Įkelti laikrodžio ekraną + Ekrano info + Eksportuoti šabloną + Pasirinktinio ekrano šablonas eksportuotas + Pakartotinai siųsti visus duomenis + Atidaryti išmaniojo laikrodžio nustatymus + Užrakintų nustatymų sąrašas + Ekranui reikalingų nustatymų sąrašas + Į ekraną įtrauktų laukų sąrašas + Bandoma įkelti pompos duomenis + BPD: seni duomenys! Nepavyksta nuskaityti pompos. + v + g + val + Neparinktas aktyvus profilis! + Profilis:\n\nLaiko perstūmimas: %1$\nProcentai: %2$d%%\" + Tikslai galioja tik APS režime! + Nėra istorijos! + Laikinas tikslas + iki + NUMATYTASIS DIAPAZONAS + tikslas + Dydis: %1$.2fv/val (%2$.2f%%) \nTrukmė %3$d min + Profilis neįkeltas + Tik APS režime! + Paskutinis rezultatas nežinomas! + UŽDARAS CIKLAS + ATVIRAS CIKLAS + CIKLAS IŠJUNGTAS + APS + Paskutinis veiksmas + Paskutinis veiksmas + %1$.2fv %1$.0f%% + Šiandien + svertinis diff --git a/plugins/sync/src/main/res/values-nb-rNO/oh_strings.xml b/plugins/sync/src/main/res/values-nb-rNO/oh_strings.xml index c33f512c0d..4ec943494a 100644 --- a/plugins/sync/src/main/res/values-nb-rNO/oh_strings.xml +++ b/plugins/sync/src/main/res/values-nb-rNO/oh_strings.xml @@ -27,7 +27,7 @@ Boluser Forlenget bolus Karbohydrater - Careportal hendelser (unntatt notater) + Helseportal-hendelser (unntatt notater) Profilbytter Totale daglige doser Midlertidige basal doser diff --git a/plugins/sync/src/main/res/values-nb-rNO/strings.xml b/plugins/sync/src/main/res/values-nb-rNO/strings.xml index e91f3141de..fca3b4dd70 100644 --- a/plugins/sync/src/main/res/values-nb-rNO/strings.xml +++ b/plugins/sync/src/main/res/values-nb-rNO/strings.xml @@ -11,7 +11,7 @@ Tillat tilkobling i roaming Lag meldinger ved feil Opprett varslinger hvis det er nødvendig med karbohydrater - Opprett varslinger i Nightscout ved feil eller meldinger (også synlig i Careportal under Behandlinger) + Opprett varslinger i Nightscout ved feil eller meldinger (også synlig i Helseportal under Behandlinger) Opprett Nightscout-meldinger ved behov for karbohydrater Synkroniserer dine data med Nightscout v1 API Synkroniserer dine data med Nightscout v3 API @@ -19,7 +19,7 @@ Blokkert på grunn av tilkoblingsalternativer Versjonen av Nightscout støttes ikke OAPS - UPLD + OPPL NSClient feil. Vurder omstart av NS og NSClient. NSCLIENT har ingen skriverettighet. Feil API-nøkkel? @@ -54,11 +54,11 @@ Motta midlertidige mål Aksepter midlertidige mål angitt med NS eller AAPSClient Motta profilbytter - Aksepter profilbytter som er angitt via NS eller NSClient + Aksepter profilbytter som er angitt via NS eller AAPSClient Motta APS offline hendelser Aksepter APS offline hendelser lagt inn gjennom NS eller AAPSClient Motta TBR og EB - Godta TBR og EB beregninger fra tilleggsmodul + Godta midlertidig basal og forlenget bolus lagt inn fra en annen instans Motta insulin Aksepter insulin angitt via NS eller AAPSClient (enhetene er ikke dosert, kun beregnet mot IOB) Motta karbohydrater @@ -116,4 +116,70 @@ Send data om glukose og behandling til xDrip+. Velg datakilde \"xDrip+ Sync Følger\" og aktiver mottak av data under Innstillinger - Inter-app innstillinger - Aksepter glukose/behandlinger Aktiver sending til xDrip+. + + + WEAR + Overvåke og kontrollere AAPS ved hjelp av WearOS-klokken. + (Ingen klokke tilkoblet) + Pumpestatus + Loopstatus + Boluskalkulator:\nBolus: %1$.2fE\nKarbo: %2$dg + Den valgte hurtigknappen er ikke lenger tilgjengelig, oppdater klokkeflis + Hurtigknapp: %1$s\nBolus: %2$.2fE\nKarbo: %3$dg + Ukjent forhåndsinnstilling midl. mål: %1$s + Avbryt gjeldende midl. mål? + Forskjellige enheter brukt på klokke og telefon! + Null-midl.mål - skal gjeldende midl. mål avbrytes? + Min-BS utenfor område! + Maks-BS utenfor område! + Midl. mål:\nMin: %1$s\nMaks: %2$s\nVarighet: %3$s + Midl. mål:\nMål: %1$s\nVarighet: %2$s + Midl. mål:\nÅrsak: %1$s\nMål: %2$s\nVarighet: %3$s + feilet - sjekk telefonen + Klokkeinnstillinger + Kontroller fra klokke + Sett midl. mål og angi behandlinger fra klokken. + Beregninger inkludert i resultatet fra kalkulator: + Generelle innstillinger + Varsle ved SMB + Vis SMB på klokken som en standard bolus. + Innstillinger for tilpasset klokkebakgrunn + Godkjenning for tilpasset klokkebakgrunn + Godkjenne at tilpasset klokkebakgrunn endrer AAPS- og klokkeinnstillinger i henhold til klokkebakgrunnens design + Tilpasset klokkebakgrunn: %1$s + Last inn klokkebakgrunn + Info urskive + Eksporter mal + Tilpasset klokkebakgrunn eksportert + Send alle data på nytt + Åpne Innstillinger på klokken + Liste over forhåndsvalg låst av urskive + Liste over innstillinger som kreves av klokkebakgrunnen + Liste over felt som er inkludert i urskive + prøver å lese data fra pumpen. + TDD: Fortsatt gamle data! Kan ikke lese fra pumpe. + E + g + t + Det er ikke angitt noen aktiv profil! + Profil:\n\nTidsforskyving: %1$d\nProsent: %2$d%% + Mål gjelder bare i APS-modus! + Ingen historikkdata! + Midl. mål + inntil + STANDARD OMRÅDE + målverdi + Tilførsel: %1$.2fE/t (%2$.2f%%) \nVarighet %3$d min + Ingen profil valgt + Bare bruk i APS-modus! + Siste resultat ikke tilgjengelig! + LUKKET LOOP + ÅPEN LOOP + LOOP DEAKTIVERT + APS + Siste beregning + Siste utført + %1$.2fE %1$.0f%% + Idag + vektlagt diff --git a/plugins/sync/src/main/res/values-nl-rNL/strings.xml b/plugins/sync/src/main/res/values-nl-rNL/strings.xml index 9c10458502..14d5adc4d0 100644 --- a/plugins/sync/src/main/res/values-nl-rNL/strings.xml +++ b/plugins/sync/src/main/res/values-nl-rNL/strings.xml @@ -116,4 +116,72 @@ Verzend glucose en behandelingsgegevens naar xDrip+. Gegevensbron \"xDrip+ Sync Follower\" moet worden geselecteerd en het accepteren van gegevens moet worden ingeschakeld in Instellingen - Inter-app instellingen - Accepteer Glucose/Behandelingen Activeer uitzendingen naar xDrip+. + + Garmin + Verbinding met Garmin apparaat (Fenix, Edge, …) + Garmin instellingen + + Monitor en bedien AAPS met uw WearOS horloge. + (Geen horloge verbonden) + Pomp status + Loop status + Reken. Wizard:\nInsuline: %1$.2fE\nKoolhy.: %2$dg + Geselecteerde QuickWizard is niet meer beschikbaar, vernieuw uw tegel + QuickWizard: %1$s\nInsuline: %2$.2fE\nKoolhy.: %3$dg + Tijdelijke doel onbekende preset: %1$s + Huidige tijdelijk streefdoel annuleren? + Verschillende eenheden gebruikt op horloge en telefoon! + Tijdelijk streefdoel 0 minuten, huidige tijdelijk streefdoel annuleren? + Min BG buiten bereik! + Max BG buiten bereik! + Tijdelijk streefdoel:\nMin: %1$s\nMax: %2$s\nDuur: %3$s + Tijdelijk streefdoel:\nDoel: %1$s\nDuur: %2$s + Tijdelijk streefdoel:\nReden: %1$s\nDoel: %2$s\nDuur: %3$s + Niet geslaagd - controleer de telefoon + Wear instellingen + Bedieningen via horloge + Stel tijdelijke doelen en bolussen in vanop je horloge. + Berekeningen inclusief in het resultaat van de wizard + Algemene instellingen + Waarschuw bij SMB + Toon SMB op horloge zoals gewone bolussen. + Aanpasbare Wijzerplaat Instellingen + Aanpasbare Wijzerplaat Autorisatie + Sta geladen aanpasbare wijzerplaat toe om sommige weergave-instellingen van horloge te wijzigen en te vergrendelen voor het ontwerp van de wijzerplaat + Aanpasbare Watchface: %1$s + Laad Watchface + Watchface informatie + Exporteer template + Aanpasbare watchface template geëxporteerd + Update Wear gegevens + Open instellingen op Wear + Lijst met instellingen die zijn vergrendeld door watchface + Lijst met instellingen vereist voor watchface + Lijst van velden opgenomen in de watchface + proberen gegevens van de pomp op te halen. + TDD: Nog steeds oude gegevens! Kan niet laden van de pomp. + E + g + u + Geen actieve profielwissel! + Profiel:\n\nTijdverschuiving: %1$d\nPercentage: %2$d%%\" + Streefdoelen zijn alleen van toepassing in de APS modus! + Geen geschiedenisgegevens! + Tijdelijk streefdoel + tot + Standaard streefbereik + streefwaarde + Basaal: %1$.2fE/uur (%2$.2f%%) \nDuur %3$d min + Geen profiel geladen + Alleen gebruiken in de APS modus! + Laatste resultaat niet beschikbaar! + GESLOTEN LOOP + OPEN LOOP + LOOP UITGESCHAKELD + APS + Laatst uitgevoerd + Laatste uitvoering + %1$.2fE %1$.0f%% + Vandaag + gewogen diff --git a/plugins/sync/src/main/res/values-pl-rPL/strings.xml b/plugins/sync/src/main/res/values-pl-rPL/strings.xml index 8f9b6b5c0e..744ac8877e 100644 --- a/plugins/sync/src/main/res/values-pl-rPL/strings.xml +++ b/plugins/sync/src/main/res/values-pl-rPL/strings.xml @@ -116,4 +116,69 @@ Wyślij dane dotyczące glikemii i leczenia do xDrip+. W ustawieniach xDrip+ należy ustawić \"Sprzętowe źródło danych\" na \"xDrip+ Sync Follower\" oraz włączyć akceptowanie danych: \"Ustawienia innych aplikacji\" - \"Akceptuj Glukozę/Akceptuj zabiegi\" Włącz nadawanie do xDrip+. + + + Monitoruj i steruj AAPS używając zegarka z WearOS. + (Brak połączonego zegarka) + Status pompy + Status pętli + Kalkulator:\nInsulina: %1$.2fU\nWęgle: %2$dg + Wybrany szybki bolus nie jest już dostępny, odśwież swój kafelek + Szybki bolus: %1$s\nInsulina: %2$.2fU\nWęgle: %3$dg + Nieznane ustawienie celu tymczasowego: %1$s + Anulować bieżący cel tymczasowy? + Różne jednostki używane na zegarku i telefonie! + Zerowy cel tymczasowy - anulować bieżący cel tymczasowy? + Min-BG poza zakresem! + Max-BG poza zakresem! + Cel tymczasowy:\nMin: %1$s\nMax: %2$s\nCzas trwania: %3$s + Cel tymczasowy:\nCel: %1$s\nCzas trwania: %2$s + Cel tymczasowy:\nPowód: %1$s\nCel: %2$s\nCzas trwania: %3$s + nie udało się - proszę sprawdzić telefon + Ustawienia Wear + Sterowanie z zegarka + Ustawiaj wartości docelowe i wprowadzaj leczenie z zegarka. + Obliczenia uwzględnione w wynikach kreatora: + Ustawienia ogólne + Powiadom na SMB + Pokaż SMB na zegarku jak bolus standardowy. + Ustawienia niestandardowej tarczy + Uprawnienia niestandardowej tarczy + Upoważnij załadowaną niestandardową tarczę zegarka, aby mogła zmieniać i zablokować niektóre ustawienia wyświetlacza zegarka w celu dopasowania ich do tarczy + Niestandardowa tarcza: %1$s + Załaduj Tarczę + O tarczy + Eksportuj szablon + Szablon tarczy niestandardowej wyeksportowany + Prześlij ponownie wszystkie dane + Otwórz ustawienia dla Wear + Lista opcji zablokowanych przez tarczę + Lista opcji wymaganych przez tarczę + Lista pól dołączonych do tarczy + próbuję pobrać dane z pompy. + TDD: Nadal stare dane! Nie można załadować z pompy. + U + g + h + Nie ustawiono aktywnego profilu! + Profil:\n\nZmiana czasu: %1$d\nProcent: %2$d%%\" + Cele mają zastosowanie tylko w trybie APS! + Brak danych historycznych! + Cel tymczasowy + do + ZAKRES DOMYŚLNY + cel + Dawka: %1$.2fU/h (%2$.2f%%) \nCzas trwania %3$d min + Nie załadowano profilu + Stosuje się tylko w trybie APS! + Ostatni wynik niedostępny! + ZAMKNIĘTA PĘTLA + OTWARTA PĘTLA + PĘTLA WYŁĄCZONA + APS + Ostatnie uruchomienie + Ostatnie działanie + %1$.2fU %1$.0f%% + Dziś + ważone diff --git a/plugins/sync/src/main/res/values-pt-rBR/strings.xml b/plugins/sync/src/main/res/values-pt-rBR/strings.xml index c0ee828626..6a39155470 100644 --- a/plugins/sync/src/main/res/values-pt-rBR/strings.xml +++ b/plugins/sync/src/main/res/values-pt-rBR/strings.xml @@ -116,4 +116,69 @@ Enviar dados de glicose e tratamentos para xDrip+. A fonte de dados \"xDrip+ Sync Seguidor\" deve ser selecionada e a aceitação de dados deve estar ativada nas configurações - Configurações entre apps - Aceitar Glucose/Tratamentos Ativar transmissões para xDrip+. + + + Monitore e controle AndroidAPS usando seu relógio WearOS. + (Nenhum relógio conectado) + Estado da Bomba + Status do loop + Calculadora:\nInsulin: %1$.2fU\nCarbs: %2$dg + O assistente rápido selecionado não está mais disponível, atualize seu atalho + Assistente rápido: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg + Alvo temporário pré-definido desconhecido: %1$s + Cancelando Alvos temporários em execução? + Unidades diferentes usadas no relógio e telefone! + Desligar alvo temporário - cancelando Alvos temporários atuais? + Glicemia mínima fora do alvo! + Glicemia maxima fora da meta! + Alvo temporário:\nMin: %1$s\nMax: %2$s\nDuração: %3$s + Alvo temporário:\nTarget: %1$s\nDuration: %2$s + Tempo no alvo:\nMotivo: %1$s\nAlvo: %2$s\nDuração: %3$s + não foi bem sucedido - por favor, verifique o telefone + Definições Wear + Controles do Relógio + Definir Alvo-Temp and inserir Tratamentos do relógio. + Resultado cálculos incluídos no Assistente: + Configurações gerais + Notificar no SMB + Mostrar SMB no relogio como bolus normal. + Configurações personalizadas de Watchface + Autorização de Watchface Personalizada + Autorizar watchface carregado e carregado para alterar e bloquear algumas configurações de relógio para o design do watchface + Watchface Personalizado: %1$s + Carregar Watchface + Infos Watchface + Exportar modelo + Modelo de watchface personalizado exportado + Reenviar Todos os Dados + Abrir Definições em Wear + Lista de preferências bloqueadas pela Watchface + Lista de preferências solicitadas pela Watchface + Lista de campos incluídos na Watchface + tentando buscar dados da Bomba. + TDD: Dados ainda antigos! Não é possível carregar da bomba. + U + g + h + Nenhum perfil ativo definido! + Perfil:\n\nFuso: %1$d\nPorcentagem: %2$d%%\" + Alvos só se aplicam no modo APS! + Nenhum dado no histórico! + Alvos Temporários + até + DEFAULT RANGE + alvo + Taxa: %1$.2fU/h (%2$.2f%%) \nDuração %3$d min + Nenhum perfil selecionado + Aplique somente no modo APS! + Último resultado indisponível! + LOOP FECHADO + LOOP ABERTO + LOOP DESATIVADO + APS + Última execução + Ultima execução + %1$.2f / %1$.0f U%% + Hoje + ponderado diff --git a/plugins/sync/src/main/res/values-pt-rPT/oh_strings.xml b/plugins/sync/src/main/res/values-pt-rPT/oh_strings.xml index 997e23a3ff..775cabf3b1 100644 --- a/plugins/sync/src/main/res/values-pt-rPT/oh_strings.xml +++ b/plugins/sync/src/main/res/values-pt-rPT/oh_strings.xml @@ -1,5 +1,7 @@ + Open Humans + OH Open Humans permite que faça o upload dos seus dados de diabetes e os doe para projetos científicos. Terminar Sessão Instalação diff --git a/plugins/sync/src/main/res/values-pt-rPT/strings.xml b/plugins/sync/src/main/res/values-pt-rPT/strings.xml index 58189d0ef6..349321b47f 100644 --- a/plugins/sync/src/main/res/values-pt-rPT/strings.xml +++ b/plugins/sync/src/main/res/values-pt-rPT/strings.xml @@ -82,5 +82,52 @@ Mostrar IG Adicionar IG à linha de estado + Loop Desactivado + xDrip+ não está instalado + Calibração enviada para xDrip+ + + + Monitorizar e controlar a AndroidAPS utilizando o seu relógio WearOS. + (Nenhum relógio Conectado) + Estado da Bomba + Estado do loop + Calc. Assistente:\nInsulina: %1$.2fU\nHidratos: %2$dg + O assistente rápido selecionado não está mais disponível, atualize o ecrã + Assistente rápido: %1$s\nInsulina: %2$.2fU\nHidratos: %3$dg + Predefinição de alvo temporário desconhecido: %1$s + Cancelando Alvos Temporários em execução? + Diferentes unidades usadas no relógio e telefone! + Alvo-temporário-Zero - cancelar Alvos -Temporários em progresso? + Min-GLIC fora do alvo! + Máx-GLIC fora do alvo! + AlvoTemporário:\nMin: %1$s\nMax: %2$s\nDuração: %3$s + AlvoTemporário:\nAlvo: %1$s\nDuração: %2$s + AlvoTemporário:\nMotivo %1$s\nAlvo: %2$s\nDuração: %3$s + sem efeito - por favor verifique no telemóvel + Definições do Relógio + Controles do Relógio + Definir Alvo-Temp and inserir Tratamentos do relógio. + Resultado cálculos incluídos no Assistente: + Definições Gerais + Notificar no SMB + Mostrar SMB no relogio como bolus normal. + Definições da watchface predefinida + Autorização da watchface predefinida + Autorizar watchface por definição para alterar e bloquear algumas configurações de exibição do relógio para se adequarem ao design do watchface + Watchface Predefinida: %1$s + Carregar Watchface + Infos Watchface + Exportar modelo + Modelo de Watchface Predefinida exportado + Reenviar Todos os Dados + Abrir Definições no Relógio + Lista de preferências bloqueadas pela Watchface + Lista de preferências necessárias para a Watchface + Lista de campos incluídos na Watchface + TID: Ainda dados antigos! Não é possível carregar a partir da bomba. + U + h + %1$.2fU %1$.0f%% + Hoje diff --git a/plugins/sync/src/main/res/values-ro-rRO/strings.xml b/plugins/sync/src/main/res/values-ro-rRO/strings.xml index 49682dc66c..47289e1d9a 100644 --- a/plugins/sync/src/main/res/values-ro-rRO/strings.xml +++ b/plugins/sync/src/main/res/values-ro-rRO/strings.xml @@ -116,4 +116,73 @@ Trimite date despre glucoză și tratamente către xDrip+. Trebuie să fie selectată sursa de date \"Sincronizare xDrip+ Urmăritor\" și acceptarea datelor trebuie să fie activată în „Setări - Setări între aplicații - Acceptă Glucoză/Tratamente” Activează transmisiuni spre xDrip+. + + Garmin + Conexiune la dispozitivul Garmin (Fenix, Edge, …) + Setări Garmin + + CEAS + Monitorizează și controlează AAPS folosind ceasul WearOS. + (Niciun ceas conectat) + Stare pompă + Stare buclă + Calc. Asistent:\nInsulină: %1$.2fU\nCarburi: %2$dg + Asistentul rapid selectat nu mai este disponibil, vă rugăm să reîncărcați iconița + Asistent calcul: %1$s\nInsulină: %2$.2fU\nCarbohidrați: %3$dg + Presetare ȚintăTemporară necunoscută: %1$s + Se anulează rularea țintelor temporare? + Unități diferite folosite pe ceas și telefon! + Zero-Temp-Target - anulați ȚinteleTemporare actuale? + Min-BG în afara intervalului! + Max-BG în afara intervalului! + ȚintăTemporară:\nMin: %1$s\nMax: %2$s\nDurată: %3$s + ȚintăTemporară:\nȚintă: %1$s\nDurată: %2$s + ȚintăTemporarăt:\nMotiv: %1$s\nȚintă: %2$s\nDurată: %3$s + fără succes - verificați telefonul + Setări Wear + Controlare din ceas + Setare Ținte-Temporare și se introduc Tratamente din ceas. + Calcule incluse în rezultatul asistentului: + Setări generale + Notifică despre SMB + Arată SMB pe ceas ca și un bolus standard. + Cadran ceas - Setări personalizate + Autorizare cadran personalizat + Autorizează cadranul personalizat să modifice și să blocheze unele setări de afișare ale ceasului pentru a se potrivi designului cadranului + Cadran personalizat: %1$s + Încărcaţi cadranul + Info cadran + Exportă șablon + Șablon ceas personalizat exportat + Retrimite toate datele + Deschide setările pe Wear + Lista de preferințe blocate de cadranul ceasului + Lista de preferințe solicitate de cadranul ceasului + Lista câmpurilor incluse în fața ceasului + se încearcă preluarea datelor din pompă. + TDD: Date încă vechi! Nu se poate încărca din pompă. + U + g + h + Nicio schimbare de profil activă! + Profil:\n\nDecalare: %1$d\nProcent: %2$d%%\" + Țintele se aplică numai în modul APS! + Nu există date istorice! + Țintă temporară + până la + INTERVAL IMPLICIT + țintă + Rată: %1$.2fU/h (%2$.2f%%) \nDurată %3$d min + Niciun profil încărcat + Aplicați doar în modul APS! + Ultimul rezultat nu este disponibil! + BUCLĂ ÎNCHISĂ + BUCLĂ DESCHISĂ + BUCLĂ INACTIVĂ + APS + Ultima rulare + Ultima implementare + %1$.2fU %1$.0f%% + Astăzi + ponderat diff --git a/plugins/sync/src/main/res/values-ru-rRU/strings.xml b/plugins/sync/src/main/res/values-ru-rRU/strings.xml index e8ebd729ea..582e622c54 100644 --- a/plugins/sync/src/main/res/values-ru-rRU/strings.xml +++ b/plugins/sync/src/main/res/values-ru-rRU/strings.xml @@ -116,4 +116,70 @@ Отправлять данные о глюкозе и терапии на xDrip+. Источником данных должен быть выбран \"xDrip+ Sync Follower\" а в настройках между приложениями надо включить - Принимать глюкозу/терапию Включить трансляции для xDrip+. + + + WEAR + Мониторить и контролировать AAPS при помощи часов WearOS. + (Часы не подключены) + Статус помпы + Статус цикла + Мастер:\nИнсулин: %1$.2fед\nУгл: %2$dг + Выбранный мастер быстрого доступа больше недоступен, обновите плитку + Мастер: %1$s\nИнсулин: %2$.2fЕд\nУгл: %3$dg + Неизвестная конфигурация врем цели: %1$s + Отменить ВЦ? + На часах и телефоне различные единицы измерения! + Нулевая врем цель - отмена? + Мин ГК вне диапазона! + Макс ГК вне диапазона! + ВЦ:\nМин: %1$s\nМакс.: %2$s\nДлительность: %3$s + ВЦ:\nЦель: %1$s\nДлительность: %2$s + ВремЦель:\nПричина: %1$s\nЦель.: %2$s\nДлительность: %3$s + неуспешно - проверьте телефон + Настройки смарт-часов на Wear OS + Контроль с часов + Ставить временные цели и вводить терапию с часов. + Расчеты включены в результат калькулятора: + Общие настройки + Оповещать об СМБ + Показывать СМБ на часах как стандартный болюс. + Настройка циферблатов + Авторизация пользовательских циферблатов + Авторизовать загруженные пользовательские циферблаты для изменения и блокировки некоторых параметров отображения часов в соответствии с дизайном часов + Пользовательский циферблат %1$s + Загрузить циферблат + Циферблат Infos + Экспортировать шаблон + Пользовательский шаблон циферблата экспортирован + Повторить отправку всех данных + Открыть настройки на часах + Список настроек, блокируемых циферблатом + Список настроек, требующихся для циферблата + Список полей, входящих в циферблат + пытаюсь получить данные с помпы. + TDD: До сих пор старые данные! Невозможно загрузить с помпы. + ед + г + ч + Активный профиль не установлен! + Профиль:\n\nСдвиг по времени: %1$d$\nПроцент: %2$d%%\" + Цели применяются только в режиме APS! + Нет данных истории! + Врем. цель + до + ДИАПАЗОН ПО УМОЛЧАНИЮ + целевое значение ГК: + Скорость: %1$.2fед/ч (%2$.2f%%) \nПродолжительность %3$d мин + Профиль не загружен + Применять только в режиме APS! + Последний результат недоступен! + ЗАМКНУТЫЙ ЦИКЛ + ОТКРЫТЫЙ ЦИКЛ + ЦИКЛ ВЫКЛЮЧЕН + APS + Предыдущее выполнение + Предыдущая активация + %1$.2fед%1$.0f%% + Сегодня + взвешенный diff --git a/plugins/sync/src/main/res/values-sk-rSK/strings.xml b/plugins/sync/src/main/res/values-sk-rSK/strings.xml index 35072544f9..dc9edf52bf 100644 --- a/plugins/sync/src/main/res/values-sk-rSK/strings.xml +++ b/plugins/sync/src/main/res/values-sk-rSK/strings.xml @@ -116,4 +116,72 @@ Poslať dáta o glykémii a ošetrení do xDrip+. Musí byť vybraný zdroj dát \"xDrip+ Sync Follower\" a prijímanie dát musí byť povolené v Nastavenia - Nastavenie komunikácie medzi aplikáciami - Prijímať Glykémie/Ošetrenia Povoliť odosielanie do xDrip+. + + Garmin + Pripájanie k zariadeniu Garmin (Fénix, Edge, ...) + Garmin nastavenia + + Zobrazovanie stavu a riadenie AndroidAPS z hodiniek s WearOS. + (Žiadne hodinky nie sú pripojené) + Stav pumpy + Stav uzavretého okruhu + Kalkulačka: \nInzulín: %1$.2fJI\nSacharidy: %2$dg + Vybraný rýchly bolus už nie je k dispozícii, obnovte prosím dlaždicu + Rýchly bolus: %1$s\nInzulín: %2$.2fJI\nSacharidy: %3$dg + Dočasný cieľ neznáma predvoľba: %1$s + Zrušenie bežiaceho dočasného cieľa? + Použité rozdielne jednotky v hodinkách a v telefóne! + Nulový dočasný cieľ - zrušenie bežiaceho dočasného cieľa? + Minimálna glykémia mimo rozsah! + Maximálna glykémia mimo rozsah! + Doč. cieľ:\nMin: %1$s\nMax: %2$s\nTrvanie: %3$s + Doč. cieľ:\nCieľ: %1$s\nTrvanie: %2$s + Doč. cieľ:\nDôvod: %1$s\nCieľ: %2$s\nTrvanie: %3$s + Neúspešné - skontrolujte telefón + Nastavenie hodiniek + Ovládanie z hodiniek + Nastavovanie dočasných cieľov a vkladanie ošetrení hodinkami. + Kalkulácia použitá vo výsledku wizardu: + Všeobecné nastavenia + Oznámenie pri SMB + Ukazovať SMB na hodinkách ako normálny bolus. + Nastavenie vlastného ciferníka + Autorizácia vlastného ciferníka + Autorizujte načítaný vlastný ciferník, aby se zmenili a uzamkli niektoré nastavenia hodiniek tak, aby vyhovovali designu ciferníka + Vlastný ciferník: %1$s + Nahrať ciferník + Informácie o ciferníku + Exportovať šablónu + Vlastná šablóna ciferníka exportovaná + Všetky dáta poslať znova + Otvoriť nastavenia na hodinkách + Zoznam preferencií uzamknutý hodinkami + Zoznam preferencií potrebných pre ciferník + Zoznam polí zahrnutých do ciferníka + pokus o načítanie dát z pumpy. + CDD: Stále staré dáta! Nie je možné načítať z pumpy. + JI + g + h + Nie je nastavený žiadny aktívny profil! + Profil:\n\nPosunutie: %1$d\nPercento: %2$d%%\" + Ciele sa použijú iba v režime APS! + Žiadne údaje o histórii! + Dočasný cieľ + až do + ŠTANDARDNÝ ROZSAH + cieľ + Rýchlosť: %1$.2fJI/h (%2$.2f%%) \nTrvanie %3$d min + Nie je vybraný žiadny profil + Použiť iba v APS móde! + Posledný výsledok nie je k dispozícii! + UZAVRETÝ OKRUH + OTVORENÝ OKRUH + UZAVRETÝ OKRUH DEAKTIVOVANÝ + APS + Posledné spustenie + Naposledy vykonané + %1$.2fJI %1$.0f%% + Dnes + vážený diff --git a/plugins/sync/src/main/res/values-sr-rCS/strings.xml b/plugins/sync/src/main/res/values-sr-rCS/strings.xml index 6822d7088f..8233071baa 100644 --- a/plugins/sync/src/main/res/values-sr-rCS/strings.xml +++ b/plugins/sync/src/main/res/values-sr-rCS/strings.xml @@ -4,8 +4,31 @@ Sinhronizuje vaše podatke sa Nightscout-om Nepodržana verzija Nightscout-a + Opcije alarma + Kreirajte obaveštenja od NS alarma + Kreirajte obaveštenja od NS najava + Prag za zastarele podatake [min] + Prag za hitno zastarele podatke [min] + Loop Onesposobljen + + + SAT + Pratite i kontrolirajte AAPS koristeći svoj WearOS sat. + (Nema povezanog sata) + Status pumpe + Loop status + Kalk. čarobnjak:\nInsulin: %1$.2fU\nUH: %2$dg + Izabrani brzi čarobnjak više nije dostupan, molimo osvežite svoju pločicu + Brzi čarobnjak: %1$s\nInsulin: %2$.2fU\nUH: %3$dg + Privremeni cilj je nepoznato podešen: %1$s + Otkazivanje pokrenutih privremenih ciljeva? + Različite jedinice se koriste na satu i telefonu! + Nulti privremeni cilj - otkazivanje pokrenutih privremenih ciljeva? + Minimalni ŠUK izvan opsega! + MaksimalnI ŠUK izvan opsega! + J diff --git a/plugins/sync/src/main/res/values-sv-rSE/strings.xml b/plugins/sync/src/main/res/values-sv-rSE/strings.xml index c5f5840e5d..5f58b2fb03 100644 --- a/plugins/sync/src/main/res/values-sv-rSE/strings.xml +++ b/plugins/sync/src/main/res/values-sv-rSE/strings.xml @@ -116,4 +116,70 @@ Skicka glukos och behandlingsdata till xDrip+. Datakällan \"xDrip+ Sync Follower\" måste väljas och godkännande av data måste aktiveras i Inställningar - Inter-app-inställningar - Acceptera Glukos/Behandlingar Aktivera sändningar till xDrip+. + + + Wear + Följ och kontrollera AAPS med din Wear OS-klocka. + (Ingen klocka ansluten) + Pumpstatus + Loop-status + Kalkylator:\nInsulin: %1$.2fU\nKolhydrater: %2$dg + Vald snabbknapp inte längre tillgänglig. Vänligen uppdatera vyn + Snabbsteg: %1$s\nInsulin: %2$.2fU\nKolhydrater: %3$dg + Tempmål okänd förinställning: %1$s + Avbryt temp-mål? + Olika enheter på klocka och telefon! + Noll-temp - avbyta nuvarande temp-mål? + Ogiltigt minimum BG! + Ogiltigt maximum BG! + Temp-mål:\nMin: %1$s\nMax: %2$s\nVaraktighet: %3$s + Temp-mål:\nMål: %1$s\nDuration: %2$s + Tillfälligt mål:\nOrsak: %1$s\nMål: %2$s\nVaraktighet: %3$s + misslyckat - kontrollera telefonen + Inställningar för klocka (Wear) + Kontrollera från klockan + Sätt temp målvärde och ange behandlingar från klockan. + Kalkyler inkluderade i resultatet + Generella inställningar + Skicka notis vid SMB + Visa SMB på klockan som en standardbolus. + Anpassade inställningar för urtavla + Anpassad auktorisering för urtavla + Auktorisera inlästa anpassade urtavlor för att ändra och låsa vissa visningsinställningar för att passa design av urtavlan + Anpassad urtavla: %1$s + Ladda urtavla + Info urtavla + Exportera mall + Anpassad mall exporterad + Uppdatera klockans data + Öppna inställningar på klockan + Inställningar som är låsta av urtavlan + Inställningar som krävs av urtavlan + Fält som ingår i urtavlan + försöker hämta data från pump. + TDD: Fortfarande gamla data! Kan inte hämta från pump. + U + g + h + Ingen aktiv profil! + Profil:\n\nTidsförskjutning: %1$d\nProcent: %2$d%%\" + Målen gäller endast i APS-läge! + Inget historiskt data! + Tillfälligt målvärde + tills + STANDARDINTERVALL + målvärde + Dos: %1$.2fU/h (%2$.2f%%) \nVaraktighet %3$d min + Ingen profil inläst + Använd endast i APS-läge! + Senaste resultat ej tillgängligt! + CLOSED LOOP + OPEN LOOP + LOOP INAKTIVERAD + APS + Kördes senast + Senaste handling + %1$.2fU %1$.0f%% + Idag + viktad diff --git a/plugins/sync/src/main/res/values-tr-rTR/strings.xml b/plugins/sync/src/main/res/values-tr-rTR/strings.xml index 73324e747f..06c6066a7a 100644 --- a/plugins/sync/src/main/res/values-tr-rTR/strings.xml +++ b/plugins/sync/src/main/res/values-tr-rTR/strings.xml @@ -116,4 +116,75 @@ KŞ ve tedavi verilerini xDrip+\'a gönderin. Veri Kaynağı \"xDrip+ Sync Follower\" seçilmeli ve Ayarlar - Uygulamalar arası ayarlar - KŞ/Tedavileri Kabul Et bölümünde verilerin kabul edilmesi etkinleştirilmelidir. xDrip+ \'a yayınları etkinleştirin. + DBRO + Verileri Garmin\'in G-Watch Wear Uygulamasına yayınlayın + + Garmin + Garmin cihazına bağlantı (Fenix, Edge,…) + Garmin ayarları + + WEAR + WearOS saatinizi kullanarak AAPS\'yi izleyin ve kontrol edin. + (Saat Bağlı Değil) + Pompa durumu + Döngü durumu + Hesap Mak.:\nİnsulin: %1$.2fÜ\nKarb: %2$dg + Seçili hızlı asistan artık mevcut değil, lütfen kutucuğu yenileyin + Hızlı Asistan: %1$s\nİnsülin: %2$.2fU\nKarb: %3$dg + Geiçici hedef bilinmeyen ön ayarı: %1$s + Çalışan Geçici-Hedefler iptal edilsin mi? + Saatte ve telefonda farklı birimler kullanılıyor! + Sıfır-Geçici-Hedef - Çalışan Geçici-Hedefler iptal edilsin mi? + Min-KŞ aralık dışında! + Maks-KŞ aralık dışında! + Geçici Hedef:\nMin: %1$s\nMaks: %2$s\nSüre: %3$s + Geçici Hedef:\nHedef: %1$s\nSüre: %2$s + Geçici Hedef:\nNeden: %1$s\nHedef: %2$s\nSüre: %3$s + başarısız - lütfen telefonu kontrol edin + Wear ayarları + Saat tarafından kontrol + Tedavileri ve Geçici hedefleri saat tarafından girin. + Sihirbaz sonucuna dahil edilen hesaplamalar: + Genel Ayarlar + SMB\'yi bildir + Saatte SMB\'yi standart bir bolus gibi göster. + Özel Saat arayüzü Ayarları + Özel Saat arayüzü Yetkilendirmesi + Bazı saat ekranı ayarlarını saatarayüzü tasarımına uyacak şekilde değiştirmek ve kilitlemek için, yüklenen özel saat arayüzüne yetki verin + Özel Saat arayüzü: %1$s + Saat arayüzü yükle + Saat arayüzü bilgisi + Şablonu Dışarı Aktar + Saat arayüzü şablonu dışa aktarıldı + Tüm verileri yeniden gönderin + Wear\'de ayarları aç + Saat arayüzü tarafından kilitlenen tercihlerin listesi + Saat arayüzü için gerekli tercihlerin listesi + Saat arayüzüne dahil edilen alanların listesi + pompadan veri almaya çalışıyor. + TGD: Hala eski veriler! Pompadan yüklenemiyor. + Ü + gr + sa + Etkin profil ayarlanmadı! + Profil:\n\nZaman Kayması: %1$d\nYüzde: %2$d%%\" + Hedefler yalnızca APS modunda uygulanır! + Geçmiş verisi yok! + Geçici Hedef + kadar + VARSAYILAN ARALIK + hedef + Oran: %1$.2fÜ/sa (%2$.2f%%) \nSüre %3$d dk + Profil yüklenmedi + Sadece APS modunda uygulayın! + Son sonuç mevcut değil! + KAPALI DÖNGÜ + AÇIK DÖNGÜ + DÖNGÜ DEVRE DIŞI + APS (YPS) + Son Çalıştırma + Son sahne + %1$.2fÜ %1$.0f%% + Bugün + ağırlıklı diff --git a/plugins/sync/src/main/res/values-uk-rUA/oh_strings.xml b/plugins/sync/src/main/res/values-uk-rUA/oh_strings.xml index 3ea04e700d..ddbbdc934b 100644 --- a/plugins/sync/src/main/res/values-uk-rUA/oh_strings.xml +++ b/plugins/sync/src/main/res/values-uk-rUA/oh_strings.xml @@ -1,2 +1,5 @@ - + + Далі + Налаштування + diff --git a/plugins/sync/src/main/res/values-uk-rUA/strings.xml b/plugins/sync/src/main/res/values-uk-rUA/strings.xml index f8a6a779b9..f190e44235 100644 --- a/plugins/sync/src/main/res/values-uk-rUA/strings.xml +++ b/plugins/sync/src/main/res/values-uk-rUA/strings.xml @@ -1,9 +1,20 @@ + WiFi SSID + Параметри сповіщень + Створювати сповіщення з тривог NS + Створювати сповіщення з оголошень NS + Поріг застарівання даних [хв] + Терміновий поріг застарівання даних [хв] + + + Од + год + Сьогодні diff --git a/plugins/sync/src/main/res/values-zh-rCN/oh_strings.xml b/plugins/sync/src/main/res/values-zh-rCN/oh_strings.xml index a7b969fe01..0651a78f31 100644 --- a/plugins/sync/src/main/res/values-zh-rCN/oh_strings.xml +++ b/plugins/sync/src/main/res/values-zh-rCN/oh_strings.xml @@ -1,6 +1,7 @@ 开源人类项目 + OH 开源人类项目允许您上传糖尿病数据并将其捐赠给科学项目。 登出 设置 diff --git a/plugins/sync/src/main/res/values-zh-rCN/strings.xml b/plugins/sync/src/main/res/values-zh-rCN/strings.xml index 6264768d2a..50f961a666 100644 --- a/plugins/sync/src/main/res/values-zh-rCN/strings.xml +++ b/plugins/sync/src/main/res/values-zh-rCN/strings.xml @@ -86,4 +86,49 @@ + + + 手表 + (无手表连接) + 泵状态 + 闭环状态 + 计算. 向导:\n胰岛素: %1$.2fU\n碳水: %2$d克 + 选定的快速向导不再可用,请刷新 + 快速向导: %1$s\n胰岛素: %2$.2fU\n碳水: %3$d克 + 未预设的临时目标: %1$s + 取消正在运行的临时目标? + 手表和手机上使用了不同的单位! + 无临时目标-取消正在运行的临时目标? + 目标血糖最小值超出范围! + 目标血糖最大值超出范围! + 临时目标:\n最小: %1$s\n最大: %2$s\n持续时间: %3$s + 临时目标:\n目标: %1$s\n持续时间: %2$s + 临时目标:\n原因: %1$s\n目标: %2$s\n持续时间: %3$s + 未成功-请检查手机 + 手表设置 + 从手表上控制 + 设置临时目标并从手表中进行治疗操作。 + 包含在向导中的计算结果: + 常规设置 + 在 SMB 上通知 + 在手表上像显示常规大剂量一样显示SMB微型大剂量 + 导出模板 + 重新发送所有数据 + 在手表上打开设置 + 正在尝试从胰岛素泵获取数据。 + 单位 + + 无历史数据! + 临时目标 + 直到 + 缺省范围 + 目标 + 未加载配置文件 + 仅在 APS 模式下应用! + 闭环 + 开环 + 闭环禁用 + 上次运行 + 今天 + 权重 diff --git a/plugins/sync/src/main/res/values/strings.xml b/plugins/sync/src/main/res/values/strings.xml index 72f0a1e164..015419cce4 100644 --- a/plugins/sync/src/main/res/values/strings.xml +++ b/plugins/sync/src/main/res/values/strings.xml @@ -180,6 +180,78 @@ Data Broadcaster - Broadcast data to other apps like Garmin watch + DBRO + Broadcast data to Garmin\'s G-Watch Wear App + + + Garmin + Connection to Garmin device (Fenix, Edge, …) + Garmin settings + + + WEAR + Monitor and control AAPS using your WearOS watch. + (No Watch Connected) + Pump status + Loop status + Calc. Wizard:\nInsulin: %1$.2fU\nCarbs: %2$dg + Selected quickwizard no longer available, please refresh your tile + QuickWizard: %1$s\nInsulin: %2$.2fU\nCarbs: %3$dg + Temptarget unknown preset: %1$s + Cancelling running Temp-Targets? + Different units used on watch and phone! + Zero-Temp-Target - cancelling running Temp-Targets? + Min-BG out of range! + Max-BG out of range! + Temptarget:\nMin: %1$s\nMax: %2$s\nDuration: %3$s + Temptarget:\nTarget: %1$s\nDuration: %2$s + Temptarget:\nReason: %1$s\nTarget: %2$s\nDuration: %3$s + not successful - please check phone + Wear settings + Controls from Watch + Set Temp-Targets and enter Treatments from the watch. + Calculations included in the Wizard result: + General Settings + Notify on SMB + Show SMB on the watch like a standard bolus. + Custom Watchface Settings + Custom Watchface Authorization + Authorize loaded custom watchface to change and lock some watch display settings to suit watchface design + Custom Watchface: %1$s + Load Watchface + Infos Watchface + Export template + Custom watchface template exported + Resend All Data + Open Settings on Wear + List of prefs locked by the Watchface + List of prefs required for the Watchface + List of fields included into the Watchface + trying to fetch data from pump. + TDD: Still old data! Cannot load from pump. + U + g + h + No active profile switch! + Profile:\n\nTimeshift: %1$d\nPercentage: %2$d%%\" + Targets only apply in APS mode! + No history data! + Temp Target + until + DEFAULT RANGE + target + Rate: %1$.2fU/h (%2$.2f%%) \nDuration %3$d min + No profile loaded + Only apply in APS mode! + Last result not available! + CLOSED LOOP + OPEN LOOP + LOOP DISABLED + APS + Last run + Last Enact + %1$.2fU %1$.0f%% + Today + weighted \ No newline at end of file diff --git a/plugins/sync/src/main/res/xml/pref_garmin.xml b/plugins/sync/src/main/res/xml/pref_garmin.xml new file mode 100644 index 0000000000..1301693ec7 --- /dev/null +++ b/plugins/sync/src/main/res/xml/pref_garmin.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/plugins/main/src/main/res/xml/pref_wear.xml b/plugins/sync/src/main/res/xml/pref_wear.xml similarity index 100% rename from plugins/main/src/main/res/xml/pref_wear.xml rename to plugins/sync/src/main/res/xml/pref_wear.xml diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/DeltaVarEncodedListTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/DeltaVarEncodedListTest.kt new file mode 100644 index 0000000000..1902fdc4ec --- /dev/null +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/DeltaVarEncodedListTest.kt @@ -0,0 +1,192 @@ +package app.aaps.plugins.sync.garmin + + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import java.nio.ByteOrder + +internal class DeltaVarEncodedListTest { + + @Test fun empty() { + val l = DeltaVarEncodedList(100, 2) + assertArrayEquals(IntArray(0), l.toArray()) + } + + @Test fun add1() { + val l = DeltaVarEncodedList(100, 2) + l.add(10, 12) + assertArrayEquals(intArrayOf(10, 12), l.toArray()) + } + + @Test fun add2() { + val l = DeltaVarEncodedList(100, 2) + l.add(10, 16) + l.add(17, 9) + assertArrayEquals(intArrayOf(10, 16, 17, 9), l.toArray()) + } + + @Test fun add3() { + val l = DeltaVarEncodedList(100, 2) + l.add(10, 16) + l.add(17, 9) + l.add(-4, 5) + assertArrayEquals(intArrayOf(10, 16, 17, 9, -4, 5), l.toArray()) + } + + @Test fun decode() { + val bytes = ByteBuffer.allocate(6) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putChar(65044.toChar()) + bytes.putChar(33026.toChar()) + bytes.putChar(4355.toChar()) + val l = DeltaVarEncodedList(intArrayOf(-1), bytes) + assertEquals(4, l.size.toLong()) + assertArrayEquals(intArrayOf(10, 201, 8, -1), l.toArray()) + } + + @Test fun decodeUneven() { + val bytes = ByteBuffer.allocate(8) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putChar(65044.toChar()) + bytes.putChar(33026.toChar()) + bytes.putChar(59395.toChar()) + bytes.putChar(10.toChar()) + val l = DeltaVarEncodedList(intArrayOf(700), ByteBuffer.wrap(bytes.array(), 0, 7)) + assertEquals(4, l.size.toLong()) + assertArrayEquals(intArrayOf(10, 201, 8, 700), l.toArray()) + } + + @Test fun decodeInt() { + val bytes = ByteBuffer.allocate(8) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putInt(-2130510316).putInt(714755) + val l = DeltaVarEncodedList(intArrayOf(700), ByteBuffer.wrap(bytes.array(), 0, 7)) + assertEquals(4, l.size.toLong()) + assertArrayEquals(intArrayOf(10, 201, 8, 700), l.toArray()) + } + + @Test fun decodeInt1() { + val bytes = ByteBuffer.allocate(3 * 4) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putInt(-2019904035).putInt(335708683).putInt(529409) + val l = DeltaVarEncodedList(intArrayOf(1483884930, 132), ByteBuffer.wrap(bytes.array(), 0, 11)) + assertEquals(3, l.size.toLong()) + assertArrayEquals(intArrayOf(1483884910, 129, 1483884920, 128, 1483884930, 132), l.toArray()) + } + + @Test fun decodeInt2() { + val bytes = ByteBuffer.allocate(100) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes + .putInt(-1761405951) + .putInt(335977999) + .putInt(335746050) + .putInt(336008197) + .putInt(335680514) + .putInt(335746053) + .putInt(-1761405949) + val l = DeltaVarEncodedList(intArrayOf(1483880370, 127), ByteBuffer.wrap(bytes.array(), 0, 28)) + assertEquals(12, l.size.toLong()) + assertArrayEquals( + intArrayOf( + 1483879986, + 999, + 1483879984, + 27, + 1483880383, + 37, + 1483880384, + 47, + 1483880382, + 57, + 1483880379, + 67, + 1483880375, + 77, + 1483880376, + 87, + 1483880377, + 97, + 1483880374, + 107, + 1483880372, + 117, + 1483880370, + 127 + ), + l.toArray() + ) + } + + @Test fun decodeInt3() { + val bytes = ByteBuffer.allocate(2 * 4) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putInt(-2020427796).putInt(166411) + val l = DeltaVarEncodedList(intArrayOf(1483886070, 133), ByteBuffer.wrap(bytes.array(), 0, 7)) + assertEquals(1, l.size.toLong()) + assertArrayEquals(intArrayOf(1483886070, 133), l.toArray()) + } + + @Test fun decodePairs() { + val bytes = ByteBuffer.allocate(10) + bytes.order(ByteOrder.LITTLE_ENDIAN) + bytes.putChar(51220.toChar()) + bytes.putChar(65025.toChar()) + bytes.putChar(514.toChar()) + bytes.putChar(897.toChar()) + bytes.putChar(437.toChar()) + val l = DeltaVarEncodedList(intArrayOf(8, 10), bytes) + assertEquals(3, l.size.toLong()) + assertArrayEquals(intArrayOf(10, 100, 201, 101, 8, 10), l.toArray()) + } + + @Test fun encoding() { + val l = DeltaVarEncodedList(100, 2) + l.add(10, 16) + l.add(17, 9) + l.add(-4, 5) + val dataList = l.encodedData() + val byteBuffer = ByteBuffer.allocate(dataList.size * 8) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + val longBuffer = byteBuffer.asLongBuffer() + for (i in dataList.indices) { + longBuffer.put(dataList[i]) + } + byteBuffer.rewind() + byteBuffer.limit(l.byteSize) + val l2 = DeltaVarEncodedList(intArrayOf(-4, 5), byteBuffer) + assertArrayEquals(intArrayOf(10, 16, 17, 9, -4, 5), l2.toArray()) + } + + @Test fun encoding2() { + val l = DeltaVarEncodedList(100, 2) + val values = intArrayOf( + 1511636926, 137, 1511637226, 138, 1511637526, 138, 1511637826, 137, 1511638126, 136, + 1511638426, 135, 1511638726, 134, 1511639026, 132, 1511639326, 130, 1511639626, 128, + 1511639926, 126, 1511640226, 124, 1511640526, 121, 1511640826, 118, 1511641127, 117, + 1511641427, 116, 1511641726, 115, 1511642027, 113, 1511642326, 111, 1511642627, 109, + 1511642927, 107, 1511643227, 107, 1511643527, 107, 1511643827, 106, 1511644127, 105, + 1511644427, 104, 1511644727, 104, 1511645027, 104, 1511645327, 104, 1511645626, 104, + 1511645926, 104, 1511646226, 105, 1511646526, 106, 1511646826, 107, 1511647126, 109, + 1511647426, 108 + ) + + for(i in values.indices step 2) { + l.add(values[i], values[i + 1]) + } + assertArrayEquals(values, l.toArray()) + val dataList = l.encodedData() + val byteBuffer = ByteBuffer.allocate(dataList.size * 8) + byteBuffer.order(ByteOrder.LITTLE_ENDIAN) + val longBuffer = byteBuffer.asLongBuffer() + for (i in dataList.indices) { + longBuffer.put(dataList[i]) + } + byteBuffer.rewind() + byteBuffer.limit(l.byteSize) + val l2 = DeltaVarEncodedList(intArrayOf(1511647426, 108), byteBuffer) + assertArrayEquals(values, l2.toArray()) + } +} diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/GarminPluginTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/GarminPluginTest.kt new file mode 100644 index 0000000000..c0af17edfa --- /dev/null +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/GarminPluginTest.kt @@ -0,0 +1,116 @@ +package app.aaps.plugins.sync.garmin + +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.events.EventNewBG +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.database.entities.GlucoseValue +import app.aaps.shared.tests.TestBase +import dagger.android.AndroidInjector +import dagger.android.HasAndroidInjector +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.Mockito.atMost +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` +import java.net.URI +import java.time.Clock +import java.time.Instant +import java.time.ZoneId +import java.time.temporal.ChronoUnit +import java.util.concurrent.locks.Condition + +class GarminPluginTest: TestBase() { + private lateinit var gp: GarminPlugin + + @Mock private lateinit var rh: ResourceHelper + @Mock private lateinit var sp: SP + @Mock private lateinit var loopHub: LoopHub + private val clock = Clock.fixed(Instant.ofEpochMilli(10_000), ZoneId.of("UTC")) + + private var injector: HasAndroidInjector = HasAndroidInjector { + AndroidInjector { + } + } + + @BeforeEach + fun setup() { + gp = GarminPlugin(injector, aapsLogger, rh, loopHub, rxBus, sp) + gp.clock = clock + `when`(loopHub.currentProfileName).thenReturn("Default") + } + + @AfterEach + fun verifyNoFurtherInteractions() { + verify(loopHub, atMost(2)).currentProfileName + verifyNoMoreInteractions(loopHub) + } + + private val getGlucoseValuesFrom = clock.instant() + .minus(2, ChronoUnit.HOURS) + .minus(9, ChronoUnit.MINUTES) + + private fun createUri(params: Map): URI { + return URI("http://foo?" + params.entries.joinToString(separator = "&") { (k, v) -> + "$k=$v"}) + } + + private fun createHeartRate(@Suppress("SameParameterValue") heartRate: Int) = mapOf( + "hr" to heartRate, + "hrStart" to 1001L, + "hrEnd" to 2001L, + "device" to "Test_Device") + + private fun createGlucoseValue(timestamp: Instant, value: Double = 93.0) = GlucoseValue( + timestamp = timestamp.toEpochMilli(), raw = 90.0, value = value, + trendArrow = GlucoseValue.TrendArrow.FLAT, noise = null, + sourceSensor = GlucoseValue.SourceSensor.RANDOM + ) + + @Test + fun testReceiveHeartRateUri() { + val hr = createHeartRate(99) + val uri = createUri(hr) + gp.receiveHeartRate(uri) + verify(loopHub).storeHeartRate( + Instant.ofEpochSecond(hr["hrStart"] as Long), + Instant.ofEpochSecond(hr["hrEnd"] as Long), + 99, + hr["device"] as String) + } + + @Test + fun testReceiveHeartRate_UriTestIsTrue() { + val params = createHeartRate(99).toMutableMap() + params["test"] = true + val uri = createUri(params) + gp.receiveHeartRate(uri) + } + + @Test + fun testGetGlucoseValues_NoLast() { + val from = getGlucoseValuesFrom + val prev = createGlucoseValue(clock.instant().minusSeconds(310)) + `when`(loopHub.getGlucoseValues(from, true)).thenReturn(listOf(prev)) + assertArrayEquals(arrayOf(prev), gp.getGlucoseValues().toTypedArray()) + verify(loopHub).getGlucoseValues(from, true) + } + + @Test + fun testGetGlucoseValues_NoNewLast() { + val from = getGlucoseValuesFrom + val lastTimesteamp = clock.instant() + val prev = createGlucoseValue(clock.instant()) + gp.newValue = mock(Condition::class.java) + `when`(loopHub.getGlucoseValues(from, true)).thenReturn(listOf(prev)) + gp.onNewBloodGlucose(EventNewBG(lastTimesteamp.toEpochMilli())) + assertArrayEquals(arrayOf(prev), gp.getGlucoseValues().toTypedArray()) + + verify(gp.newValue).signalAll() + verify(loopHub).getGlucoseValues(from, true) + } +} diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/HttpServerTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/HttpServerTest.kt new file mode 100644 index 0000000000..d89dfa9156 --- /dev/null +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/HttpServerTest.kt @@ -0,0 +1,99 @@ +package app.aaps.plugins.sync.garmin + +import app.aaps.shared.tests.TestBase +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.net.HttpURLConnection +import java.net.SocketAddress +import java.net.URI +import java.nio.charset.Charset +import java.time.Duration + +internal class HttpServerTest: TestBase() { + + private fun toInputStream(s: String): InputStream { + return ByteArrayInputStream(s.toByteArray(Charset.forName("ASCII"))) + } + + @Test fun testReadBody() { + val input = toInputStream("Test") + assertEquals("Test", HttpServer.readBody(input, 100)) + } + + @Test fun testReadBody_MoreContentThanLength() { + val input = toInputStream("Test") + assertEquals("Tes", HttpServer.readBody(input, 3)) + } + + @Test fun testParseRequest_Get() { + val req = """ + GET http://foo HTTP/1.1 + """.trimIndent() + assertEquals( + URI("http://foo") to null, + HttpServer.parseRequest(toInputStream(req))) + } + + @Test fun testParseRequest_PostEmptyBody() { + val req = """ + POST http://foo HTTP/1.1 + """.trimIndent() + assertEquals( + URI("http://foo") to null, + HttpServer.parseRequest(toInputStream(req))) + } + + @Test fun testParseRequest_PostBody() { + val req = """ + POST http://foo HTTP/1.1 + Content-Type: application/x-www-form-urlencoded + + a=1&b=2 + """.trimIndent() + assertEquals( + URI("http://foo?a=1&b=2") to null, + HttpServer.parseRequest(toInputStream(req))) + } + + @Test fun testParseRequest_PostBodyContentLength() { + val req = """ + POST http://foo HTTP/1.1 + Content-Type: application/x-www-form-urlencoded + Content-Length: 3 + + a=1&b=2 + """.trimIndent() + assertEquals( + URI("http://foo?a=1") to null, + HttpServer.parseRequest(toInputStream(req))) + } + + @Test fun testRequest() { + val port = 28895 + val reqUri = URI("http://127.0.0.1:$port/foo") + HttpServer(aapsLogger, port).use { server -> + server.registerEndpoint("/foo") { _: SocketAddress, uri: URI, _: String? -> + assertEquals(URI("/foo"), uri) + "test" + } + assertTrue(server.awaitReady(Duration.ofSeconds(10))) + val resp = reqUri.toURL().openConnection() as HttpURLConnection + assertEquals(200, resp.responseCode) + val content = (resp.content as InputStream).reader().use { r -> r.readText() } + assertEquals("test", content) + } + } + + @Test fun testRequest_NotFound() { + val port = 28895 + val reqUri = URI("http://127.0.0.1:$port/foo") + HttpServer(aapsLogger, port).use { server -> + assertTrue(server.awaitReady(Duration.ofSeconds(10))) + val resp = reqUri.toURL().openConnection() as HttpURLConnection + assertEquals(404, resp.responseCode) + } + } +} diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/LoopHubTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/LoopHubTest.kt new file mode 100644 index 0000000000..6f1cccad86 --- /dev/null +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/garmin/LoopHubTest.kt @@ -0,0 +1,201 @@ +package app.aaps.plugins.sync.garmin + + +import app.aaps.core.interfaces.aps.APSResult +import app.aaps.core.interfaces.aps.Loop +import app.aaps.core.interfaces.constraints.ConstraintsChecker +import app.aaps.core.interfaces.db.GlucoseUnit +import app.aaps.core.interfaces.iob.IobCobCalculator +import app.aaps.core.interfaces.iob.IobTotal +import app.aaps.core.interfaces.logging.UserEntryLogger +import app.aaps.core.interfaces.profile.Profile +import app.aaps.core.interfaces.profile.ProfileFunction +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.database.ValueWrapper +import app.aaps.database.entities.EffectiveProfileSwitch +import app.aaps.database.entities.GlucoseValue +import app.aaps.database.entities.HeartRate +import app.aaps.database.entities.embedments.InsulinConfiguration +import app.aaps.database.impl.AppRepository +import app.aaps.database.impl.transactions.InsertOrUpdateHeartRateTransaction +import app.aaps.shared.tests.TestBase +import io.reactivex.rxjava3.core.Completable +import io.reactivex.rxjava3.core.Single +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` +import java.time.Clock +import java.time.Instant +import java.time.ZoneId + +class LoopHubTest: TestBase() { + @Mock lateinit var commandQueue: CommandQueue + @Mock lateinit var constraints: ConstraintsChecker + @Mock lateinit var iobCobCalculator: IobCobCalculator + @Mock lateinit var loop: Loop + @Mock lateinit var profileFunction: ProfileFunction + @Mock lateinit var repo: AppRepository + @Mock lateinit var userEntryLogger: UserEntryLogger + + private lateinit var loopHub: LoopHubImpl + private val clock = Clock.fixed(Instant.ofEpochMilli(10_000), ZoneId.of("UTC")) + + @BeforeEach + fun setup() { + loopHub = LoopHubImpl(iobCobCalculator, loop, profileFunction, repo) + loopHub.clock = clock + } + + @AfterEach + fun verifyNoFurtherInteractions() { + verifyNoMoreInteractions(commandQueue) + verifyNoMoreInteractions(constraints) + verifyNoMoreInteractions(iobCobCalculator) + verifyNoMoreInteractions(loop) + verifyNoMoreInteractions(profileFunction) + verifyNoMoreInteractions(repo) + verifyNoMoreInteractions(userEntryLogger) + } + + @Test + fun testCurrentProfile() { + val profile = mock(Profile::class.java) + `when`(profileFunction.getProfile()).thenReturn(profile) + assertEquals(profile, loopHub.currentProfile) + verify(profileFunction, times(1)).getProfile() + } + + @Test + fun testCurrentProfileName() { + `when`(profileFunction.getProfileName()).thenReturn("pro") + assertEquals("pro", loopHub.currentProfileName) + verify(profileFunction, times(1)).getProfileName() + } + + @Test + fun testGlucoseUnit() { + val profile = mock(Profile::class.java) + `when`(profile.units).thenReturn(GlucoseUnit.MMOL) + `when`(profileFunction.getProfile()).thenReturn(profile) + assertEquals(GlucoseUnit.MMOL, loopHub.glucoseUnit) + verify(profileFunction, times(1)).getProfile() + } + + @Test + fun testGlucoseUnitNullProfile() { + `when`(profileFunction.getProfile()).thenReturn(null) + assertEquals(GlucoseUnit.MGDL, loopHub.glucoseUnit) + verify(profileFunction, times(1)).getProfile() + } + + @Test + fun testInsulinOnBoard() { + val iobTotal = IobTotal(time = 0).apply { iob = 23.9 } + `when`(iobCobCalculator.calculateIobFromBolus()).thenReturn(iobTotal) + assertEquals(23.9, loopHub.insulinOnboard, 1e-10) + verify(iobCobCalculator, times(1)).calculateIobFromBolus() + } + + @Test + fun testIsConnected() { + `when`(loop.isDisconnected).thenReturn(false) + assertEquals(true, loopHub.isConnected) + verify(loop, times(1)).isDisconnected + } + + private fun effectiveProfileSwitch(duration: Long) = EffectiveProfileSwitch( + timestamp = 100, + basalBlocks = emptyList(), + isfBlocks = emptyList(), + icBlocks = emptyList(), + targetBlocks = emptyList(), + glucoseUnit = EffectiveProfileSwitch.GlucoseUnit.MGDL, + originalProfileName = "foo", + originalCustomizedName = "bar", + originalTimeshift = 0, + originalPercentage = 100, + originalDuration = duration, + originalEnd = 100 + duration, + insulinConfiguration = InsulinConfiguration( + "label", 0, 0 + ) + ) + + @Test + fun testIsTemporaryProfileTrue() { + val eps = effectiveProfileSwitch(10) + `when`(repo.getEffectiveProfileSwitchActiveAt(clock.millis())).thenReturn( + Single.just(ValueWrapper.Existing(eps))) + assertEquals(true, loopHub.isTemporaryProfile) + verify(repo, times(1)).getEffectiveProfileSwitchActiveAt(clock.millis()) + } + + @Test + fun testIsTemporaryProfileFalse() { + val eps = effectiveProfileSwitch(0) + `when`(repo.getEffectiveProfileSwitchActiveAt(clock.millis())).thenReturn( + Single.just(ValueWrapper.Existing(eps))) + assertEquals(false, loopHub.isTemporaryProfile) + verify(repo).getEffectiveProfileSwitchActiveAt(clock.millis()) + } + + @Test + fun testTemporaryBasal() { + val apsResult = mock(APSResult::class.java) + `when`(apsResult.percent).thenReturn(45) + val lastRun = Loop.LastRun().apply { constraintsProcessed = apsResult } + `when`(loop.lastRun).thenReturn(lastRun) + assertEquals(0.45, loopHub.temporaryBasal, 1e-6) + verify(loop).lastRun + } + + @Test + fun testTemporaryBasalNoRun() { + `when`(loop.lastRun).thenReturn(null) + assertTrue(loopHub.temporaryBasal.isNaN()) + verify(loop, times(1)).lastRun + } + + @Test + fun testGetGlucoseValues() { + val glucoseValues = listOf( + GlucoseValue( + timestamp = 1_000_000L, raw = 90.0, value = 93.0, + trendArrow = GlucoseValue.TrendArrow.FLAT, noise = null, + sourceSensor = GlucoseValue.SourceSensor.DEXCOM_G5_XDRIP)) + `when`(repo.compatGetBgReadingsDataFromTime(1001_000, false)) + .thenReturn(Single.just(glucoseValues)) + assertArrayEquals( + glucoseValues.toTypedArray(), + loopHub.getGlucoseValues(Instant.ofEpochMilli(1001_000), false).toTypedArray()) + verify(repo).compatGetBgReadingsDataFromTime(1001_000, false) + } + + @Test + fun testStoreHeartRate() { + val samplingStart = Instant.ofEpochMilli(1_001_000) + val samplingEnd = Instant.ofEpochMilli(1_101_000) + val hr = HeartRate( + timestamp = samplingStart.toEpochMilli(), + duration = samplingEnd.toEpochMilli() - samplingStart.toEpochMilli(), + dateCreated = clock.millis(), + beatsPerMinute = 101.0, + device = "Test Device") + `when`(repo.runTransaction(InsertOrUpdateHeartRateTransaction(hr))).thenReturn( + Completable.fromCallable { + InsertOrUpdateHeartRateTransaction.TransactionResult( + emptyList(), emptyList())}) + loopHub.storeHeartRate( + samplingStart, samplingEnd, 101, "Test Device") + verify(repo).runTransaction(InsertOrUpdateHeartRateTransaction(hr)) + } +} \ No newline at end of file diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3PluginTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3PluginTest.kt index af8d1f2fc8..75fba02329 100644 --- a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3PluginTest.kt +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/NSClientV3PluginTest.kt @@ -28,10 +28,8 @@ import app.aaps.database.entities.TherapyEvent import app.aaps.database.entities.embedments.InsulinConfiguration import app.aaps.database.entities.embedments.InterfaceIDs import app.aaps.database.impl.AppRepository -import app.aaps.plugins.sync.nsShared.NsIncomingDataProcessor import app.aaps.plugins.sync.nsShared.StoreDataForDbImpl import app.aaps.plugins.sync.nsclient.ReceiverDelegate -import app.aaps.plugins.sync.nsclient.data.NSDeviceStatusHandler import app.aaps.plugins.sync.nsclient.extensions.fromConstant import app.aaps.shared.tests.TestBaseWithProfile import com.google.common.truth.Truth.assertThat @@ -55,8 +53,6 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() { @Mock lateinit var nsClientSource: NSClientSource @Mock lateinit var virtualPump: VirtualPump @Mock lateinit var mockedProfileFunction: ProfileFunction - @Mock lateinit var nsDeviceStatusHandler: NSDeviceStatusHandler - @Mock lateinit var nsIncomingDataProcessor: NsIncomingDataProcessor @Mock lateinit var repository: AppRepository @Mock lateinit var persistenceLayer: PersistenceLayer @Mock lateinit var insulin: Insulin @@ -83,8 +79,8 @@ internal class NSClientV3PluginTest : TestBaseWithProfile() { sut = NSClientV3Plugin( injector, aapsLogger, aapsSchedulers, rxBus, rh, context, fabricPrivacy, - sp, receiverDelegate, config, dateUtil, uiInteraction, dataSyncSelectorV3, persistenceLayer, - nsDeviceStatusHandler, nsClientSource, nsIncomingDataProcessor, storeDataForDb, decimalFormatter + sp, receiverDelegate, config, dateUtil, dataSyncSelectorV3, persistenceLayer, + nsClientSource, storeDataForDb, decimalFormatter ) sut.nsAndroidClient = nsAndroidClient Mockito.`when`(mockedProfileFunction.getProfile(anyLong())).thenReturn(validProfile) diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/extensions/DeviceStatusExtensionKtTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/extensions/DeviceStatusExtensionKtTest.kt index f1cdf04ba3..1213d40efc 100644 --- a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/extensions/DeviceStatusExtensionKtTest.kt +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/extensions/DeviceStatusExtensionKtTest.kt @@ -5,6 +5,7 @@ import app.aaps.core.interfaces.nsclient.ProcessedDeviceStatusData import app.aaps.core.interfaces.objects.Instantiator import app.aaps.core.interfaces.resources.ResourceHelper import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.nssdk.interfaces.RunningConfiguration import app.aaps.core.nssdk.mapper.convertToRemoteAndBack @@ -16,6 +17,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.mockito.Mock +import org.mockito.Mockito @Suppress("SpellCheckingInspection") internal class DeviceStatusExtensionKtTest : TestBase() { @@ -26,6 +28,7 @@ internal class DeviceStatusExtensionKtTest : TestBase() { @Mock lateinit var config: Config @Mock lateinit var runningConfiguration: RunningConfiguration @Mock lateinit var instantiator: Instantiator + @Mock lateinit var uiInteraction: UiInteraction private lateinit var processedDeviceStatusData: ProcessedDeviceStatusData private lateinit var nsDeviceStatusHandler: NSDeviceStatusHandler @@ -33,7 +36,8 @@ internal class DeviceStatusExtensionKtTest : TestBase() { @BeforeEach fun setup() { processedDeviceStatusData = ProcessedDeviceStatusDataImpl(rh, dateUtil, sp, instantiator) - nsDeviceStatusHandler = NSDeviceStatusHandler(sp, config, dateUtil, runningConfiguration, processedDeviceStatusData) + nsDeviceStatusHandler = NSDeviceStatusHandler(sp, config, dateUtil, runningConfiguration, processedDeviceStatusData, uiInteraction, rh) + Mockito.`when`(config.NSCLIENT).thenReturn(true) } @Test diff --git a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/workers/LoadBgWorkerTest.kt b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/workers/LoadBgWorkerTest.kt index 0517d119bc..b1c0a551a6 100644 --- a/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/workers/LoadBgWorkerTest.kt +++ b/plugins/sync/src/test/kotlin/app/aaps/plugins/sync/nsclientV3/workers/LoadBgWorkerTest.kt @@ -13,7 +13,6 @@ import app.aaps.core.interfaces.receivers.ReceiverStatusStore import app.aaps.core.interfaces.resources.ResourceHelper import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.source.NSClientSource -import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.DecimalFormatter import app.aaps.core.interfaces.utils.fabric.FabricPrivacy @@ -23,9 +22,7 @@ import app.aaps.core.utils.receivers.DataWorkerStorage import app.aaps.database.entities.GlucoseValue import app.aaps.database.entities.embedments.InterfaceIDs import app.aaps.implementation.utils.DecimalFormatterImpl -import app.aaps.plugins.sync.nsShared.NsIncomingDataProcessor import app.aaps.plugins.sync.nsclient.ReceiverDelegate -import app.aaps.plugins.sync.nsclient.data.NSDeviceStatusHandler import app.aaps.plugins.sync.nsclientV3.DataSyncSelectorV3 import app.aaps.plugins.sync.nsclientV3.NSClientV3Plugin import app.aaps.plugins.sync.nsclientV3.extensions.toNSSvgV3 @@ -54,16 +51,13 @@ internal class LoadBgWorkerTest : TestBase() { @Mock lateinit var nsAndroidClient: NSAndroidClient @Mock lateinit var rh: ResourceHelper @Mock lateinit var config: Config - @Mock lateinit var uiInteraction: UiInteraction @Mock lateinit var dataSyncSelectorV3: DataSyncSelectorV3 @Mock lateinit var persistenceLayer: PersistenceLayer @Mock lateinit var receiverStatusStore: ReceiverStatusStore @Mock lateinit var nsClientSource: NSClientSource @Mock lateinit var workManager: WorkManager @Mock lateinit var workContinuation: WorkContinuation - @Mock lateinit var nsDeviceStatusHandler: NSDeviceStatusHandler @Mock lateinit var storeDataForDb: StoreDataForDb - @Mock lateinit var nsIncomingDataProcessor: NsIncomingDataProcessor @Mock lateinit var context: ContextWithInjector private lateinit var nsClientV3Plugin: NSClientV3Plugin @@ -101,8 +95,8 @@ internal class LoadBgWorkerTest : TestBase() { receiverDelegate = ReceiverDelegate(rxBus, rh, sp, receiverStatusStore, aapsSchedulers, fabricPrivacy) nsClientV3Plugin = NSClientV3Plugin( injector, aapsLogger, aapsSchedulers, rxBus, rh, context, fabricPrivacy, - sp, receiverDelegate, config, dateUtil, uiInteraction, dataSyncSelectorV3, persistenceLayer, - nsDeviceStatusHandler, nsClientSource, nsIncomingDataProcessor, storeDataForDb, decimalFormatter + sp, receiverDelegate, config, dateUtil, dataSyncSelectorV3, persistenceLayer, + nsClientSource, storeDataForDb, decimalFormatter ) nsClientV3Plugin.newestDataOnServer = LastModified(LastModified.Collections()) } diff --git a/pump/combo/src/main/res/values-nb-rNO/strings.xml b/pump/combo/src/main/res/values-nb-rNO/strings.xml index 1883de382d..b13cd45cd5 100644 --- a/pump/combo/src/main/res/values-nb-rNO/strings.xml +++ b/pump/combo/src/main/res/values-nb-rNO/strings.xml @@ -36,13 +36,13 @@ Ugyldig oppsett av pumpen. Les dokumentasjonen og sjekk at Quick Info menyen heter QUICK INFO ved hjelp av 360 programvaren. Leser basalprofil Pumpe historikken har blitt endret siden bolus kalkuleringen ble utført. Bolus har ikke blitt levert. Vennligst rekalkuler om bolus fortsatt er nødvendig. - Bolus har blitt levert, men det oppsto en feil ved loggføring i behandlinger. Dette kan oppstå hvis to små bolus på samme størrelse blir levert i løpet av to minutter. Vennligst sjekk pumpe historikken og behandlinger loggen, og bruk Careportal for å legge til de manglende behandlingene. Pass på at du ikke legger til to identiske behandlinger på samme minutt. + Bolus har blitt levert, men det oppsto en feil ved loggføring i behandlinger. Dette kan oppstå hvis to små bolus på samme størrelse blir levert i løpet av to minutter. Vennligst sjekk pumpe historikken og behandlinger loggen, og bruk Helseportal for å legge til de manglende behandlingene. Pass på at du ikke legger til to identiske behandlinger på samme minutt. Avviser høy temp target siden kalkuleringen ikke tok hensyn til nylige endringer i pumpe historikken Oppdaterer pumpestatus Basal dosen i pumpen har blitt endret og vil i løpet av kort tid bli oppdatert Basalsats endret i pumpe, men lesing av den feilet Sjekker for endringer i historikken - Flere boluser levert i samme minutt og med samme insulinmengde ble importert. Bare en av doseringene ble lagt til i behandlinger. Vennligst sjekk pumpen og legg manuelt til ekstra bolus doseringer i Careportal. Ikke legg til flere boluser i samme minutt. + Flere boluser levert i samme minutt og med samme insulinmengde ble importert. Bare en av doseringene ble lagt til i behandlinger. Vennligst sjekk pumpen og legg manuelt til ekstra bolus doseringer i Helseportal. Ikke legg til flere boluser i samme minutt. Den siste bolus er eldre enn 24t eller er i fremtiden. Vennligst sjekk at datoen i pumpen er korrekt. Tid/dato for levert bolus i pumpen er trolig feil, og IOB beregningen blir da feil. Vennligst sjekk pumpens tid/dato. Antall boluser diff --git a/pump/combov2/src/main/res/values-iw-rIL/strings.xml b/pump/combov2/src/main/res/values-iw-rIL/strings.xml index 4353ad56ba..37f974cb89 100644 --- a/pump/combov2/src/main/res/values-iw-rIL/strings.xml +++ b/pump/combov2/src/main/res/values-iw-rIL/strings.xml @@ -42,6 +42,7 @@ תם זמן סריקת Combo הצימוד נכשל בגלל השגיאה: %1$s הצימוד בוטל מסיבה לא ידועה + "אורך קוד הזיהוי אינו תקין: אורכו צריך להיות %1$d תווים, נרשמו %2$d" סורק אחר משאבות יוצר חיבור בלוטות\' (ניסיון מספר %1$d) מבצע לחיצת יד עם המשאבה @@ -96,6 +97,7 @@ לא ניתן להגדיר בזאלי זמני אם המינון הבזאלי הבסיסי הוא 0 צימוד AndroidAPS ואנדרואיד עם משאבת Accu-Chek combo שאינה מצומדת ביטול צימוד AndroidAPS ואנדרואיד ממשאבת Accu-Chek combo המצומדת + נמצא מינון בזאלי זמני לא ברור והוא נעצר; %1$d%; זמן נותר: %2$s שגיאת חיבור: %1$s חיבור אחרון: לפני %1$d דקות התראה: %s diff --git a/pump/combov2/src/main/res/values-nb-rNO/strings.xml b/pump/combov2/src/main/res/values-nb-rNO/strings.xml index c31eb9b14f..e905e4cfb5 100644 --- a/pump/combov2/src/main/res/values-nb-rNO/strings.xml +++ b/pump/combov2/src/main/res/values-nb-rNO/strings.xml @@ -100,7 +100,7 @@ knappene samtidig for å avbryte parringen)\n Lar aktive emulert 100% TBR få avslutte Ignorerer redundant 100% TBR forespørsel Uventet begrensning oppsto ved justering av TBR: målprosenten var %1$d%%, nådde grense på %2$d%% - Kan ikke sette absolutt TBR hvis basalraten er null + Kan ikke sette absolutt TBR hvis basaldosen er null Sammenkoble AndroidAPS og Android med en ikke-tilkoblet Accu-Chek Combo pumpe Koble fra AndroidAPS og Android fra den ilkoblede Accu-Chek Combo pumpen Ukjent TBR ble oppdaget og stoppet; prosent: %1$d%%, gjenværende varighet: %2$s diff --git a/pump/dana/src/main/res/values-nb-rNO/strings.xml b/pump/dana/src/main/res/values-nb-rNO/strings.xml index 211c2198e2..e561897b03 100644 --- a/pump/dana/src/main/res/values-nb-rNO/strings.xml +++ b/pump/dana/src/main/res/values-nb-rNO/strings.xml @@ -107,9 +107,9 @@ Bolus hastighet Valgt pumpe Logg reservoar bytte - Legg til \"Insulinbytte\" i Careportal når den oppdages i historikken + Legg til \"Insulinbytte\" i Helseportal når den oppdages i historikken Logg kanyle bytte - Legg til \"Kanylebytte\" i Careportal når den oppdages i historikken + Legg til \"Kanylebytte\" i Helseportal når den oppdages i historikken PIN1 PIN2 Trykk OK på pumpen\nog skriv inn de 2 viste tallene\nHold skjermen på pumpen PÅ ved å trykke minus knappen til du fullfører inntastingen. diff --git a/pump/diaconn/src/main/res/values-nb-rNO/strings.xml b/pump/diaconn/src/main/res/values-nb-rNO/strings.xml index 23b32d57b3..744ca26447 100644 --- a/pump/diaconn/src/main/res/values-nb-rNO/strings.xml +++ b/pump/diaconn/src/main/res/values-nb-rNO/strings.xml @@ -85,10 +85,10 @@ aps_incarnation_no pump_serial_no Logg reservoar bytte - Legg til \"Insulinbytte\" i Careportal når den oppdages i historikken + Legg til \"Insulinbytte\" i Helseportal når den oppdages i historikken Logg bytte av kanyle - Legg til \"Bytte av injeksjonssted\" i Careportal når den oppdages i historikken - Legg til \"Bytte av batteri\" i Careportal når den oppdages i historikken + Legg til \"Bytte av injeksjonssted\" i Helseportal når den oppdages i historikken + Legg til \"Bytte av batteri\" i Helseportal når den oppdages i historikken Logg batteri bytte Logg synkronisering pågår Lavt insulinnivå @@ -145,7 +145,7 @@ Når basal oppsett er fullført, kan basaldoseringer startes. Kommandoen ble ikke utført. Vennligst prøv igjen. Logg bytte av slangesett - Legg til \"Slangesettbytte\" i Careportal når den oppdages i historikken + Legg til \"Slangesettbytte\" i Helseportal når den oppdages i historikken Temp Basal startet Vel Lav Glukose Stopp (LGS) er injeksjoner begrenset LGS status er PÅ. PÅ kommando er nektet. diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt index e40762b526..2adb563031 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt @@ -11,6 +11,7 @@ import android.text.format.DateFormat import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import app.aaps.core.interfaces.constraints.ConstraintsChecker import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.logging.LTag @@ -39,6 +40,7 @@ import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventAppExit import app.aaps.core.interfaces.rx.events.EventDismissNotification import app.aaps.core.interfaces.rx.events.EventOverviewBolusProgress +import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.core.interfaces.ui.UiInteraction import app.aaps.core.interfaces.utils.DateUtil import app.aaps.core.interfaces.utils.DecimalFormatter @@ -67,6 +69,7 @@ import kotlin.math.abs aapsLogger: AAPSLogger, rh: ResourceHelper, commandQueue: CommandQueue, + private val sp: SP, private val constraintChecker: ConstraintsChecker, private val aapsSchedulers: AapsSchedulers, private val rxBus: RxBus, @@ -102,6 +105,9 @@ import kotlin.math.abs .toObservable(EventAppExit::class.java) .observeOn(aapsSchedulers.io) .subscribe({ context.unbindService(mConnection) }, fabricPrivacy::logException) + + // Force enable pump unreachable alert due to some failure modes of Medtrum pump + sp.putBoolean(app.aaps.core.utils.R.string.key_enable_pump_unreachable_alert, true) } override fun onStop() { @@ -134,6 +140,7 @@ import kotlin.math.abs preprocessSerialSettings(preferenceFragment) preprocessAlarmSettings(preferenceFragment) preprocessMaxInsulinSettings(preferenceFragment) + preprocessConnectionAlertSettings(preferenceFragment) } private fun preprocessSerialSettings(preferenceFragment: PreferenceFragmentCompat) { @@ -241,6 +248,21 @@ import kotlin.math.abs } } + private fun preprocessConnectionAlertSettings(preferenceFragment: PreferenceFragmentCompat) { + val unreachableAlertSetting = preferenceFragment.findPreference(rh.gs(app.aaps.core.utils.R.string.key_enable_pump_unreachable_alert)) + val unreachableThresholdSetting = preferenceFragment.findPreference(rh.gs(app.aaps.core.utils.R.string.key_pump_unreachable_threshold_minutes)) + + unreachableAlertSetting?.apply { + isSelectable = false + summary = rh.gs(R.string.enable_pump_unreachable_alert_summary) + } + + unreachableThresholdSetting?.apply { + val currentValue = text + summary = "${rh.gs(R.string.pump_unreachable_threshold_minutes_summary)}\n${currentValue}" + } + } + override fun isInitialized(): Boolean { return medtrumPump.pumpState > MedtrumPumpState.EJECTED && medtrumPump.pumpState < MedtrumPumpState.STOPPED } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt index e091cb8f73..9eb2e01e89 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/ReadDataPacket.kt @@ -1,12 +1,32 @@ package info.nightscout.pump.medtrum.comm +import CrcUtils.calcCrc8 + class ReadDataPacket(data: ByteArray) { private var totalData = data.copyOfRange(0, data.size - 1) // Strip crc + private var failed = false private var dataSize: Byte = data[0] + private var sequenceNumber: Byte = data[3] + + init { + val crcInitialChunk = calcCrc8(data.copyOfRange(0, data.size - 1), data.size - 1) + + if (crcInitialChunk != data[data.size - 1]) { + failed = true + } + } fun addData(newData: ByteArray) { totalData += newData.copyOfRange(4, newData.size - 1) // Strip header and crc + sequenceNumber++ + val crcNewChunk = calcCrc8(newData.copyOfRange(0, newData.size - 1), newData.size - 1) + if (crcNewChunk != newData[newData.size - 1]) { + failed = true + } + if (sequenceNumber != newData[3]) { + failed = true + } } fun allDataReceived(): Boolean { @@ -16,4 +36,8 @@ class ReadDataPacket(data: ByteArray) { fun getData(): ByteArray { return totalData } + + fun failed(): Boolean { + return failed + } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt index 31655f3ed9..5d3974e133 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/WriteCommandPackets.kt @@ -1,8 +1,8 @@ package info.nightscout.pump.medtrum.comm -class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { +import CrcUtils.calcCrc8 - private val CRC_8_TABLE: IntArray = intArrayOf(0, 155, 173, 54, 193, 90, 108, 247, 25, 130, 180, 47, 216, 67, 117, 238, 50, 169, 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201, 82, 165, 62, 8, 147, 125, 230, 208, 75, 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, 35, 184, 200, 83, 101, 254, 9, 146, 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, 120, 78, 213, 34, 185, 143, 20, 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, 196, 242, 105, 135, 28, 42, 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, 57, 162, 148, 15, 248, 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, 183, 44, 26, 129, 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, 218, 65, 119, 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, 145, 102, 253, 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, 224, 123) +class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { private val packages = mutableListOf() private var index = 0 @@ -17,7 +17,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { ) var tmp: ByteArray = header + data.copyOfRange(1, data.size) - val totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size).toByte() + val totalCommand: ByteArray = tmp + calcCrc8(tmp, tmp.size) if ((totalCommand.size - header.size) <= 15) { packages.add(totalCommand + 0.toByte()) @@ -28,7 +28,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { while (remainingCommand.size > 15) { header[3] = pkgIndex.toByte() tmp = header + remainingCommand.copyOfRange(0, 15) - packages.add(tmp + calcCrc8(tmp, tmp.size).toByte()) + packages.add(tmp + calcCrc8(tmp, tmp.size)) remainingCommand = remainingCommand.copyOfRange(15, remainingCommand.size) pkgIndex = (pkgIndex + 1) % 256 @@ -37,7 +37,7 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { // Add last package header[3] = pkgIndex.toByte() tmp = header + remainingCommand - packages.add(tmp + calcCrc8(tmp, tmp.size).toByte()) + packages.add(tmp + calcCrc8(tmp, tmp.size)) } } @@ -53,12 +53,4 @@ class WriteCommandPackets(data: ByteArray, sequenceNumber: Int) { fun allPacketsConsumed(): Boolean { return index >= packages.size } - - private fun calcCrc8(value: ByteArray, size: Int): Int { - var crc8 = 0 - for (i in 0 until size) { - crc8 = CRC_8_TABLE[(value[i].toInt() and 255) xor (crc8 and 255)] and 255 - } - return crc8 - } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt index 32ed8e39d6..929f574165 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt @@ -53,14 +53,53 @@ class NotificationPacket(val injector: HasAndroidInjector) { private const val MASK_STORAGE = 0x100 private const val MASK_ALARM = 0x200 private const val MASK_AGE = 0x400 - private const val MASK_UNKNOWN_1 = 0x800 + private const val MASK_MAGNETO_PLACE = 0x800 private const val MASK_UNUSED_CGM = 0x1000 private const val MASK_UNUSED_COMMAND_CONFIRM = 0x2000 private const val MASK_UNUSED_AUTO_STATUS = 0x4000 private const val MASK_UNUSED_LEGACY = 0x8000 + + private const val SIZE_FIELD_MASK = 2 + private const val SIZE_SUSPEND = 4 + private const val SIZE_NORMAL_BOLUS = 3 + private const val SIZE_EXTENDED_BOLUS = 3 + private const val SIZE_BASAL = 12 + private const val SIZE_SETUP = 1 + private const val SIZE_RESERVOIR = 2 + private const val SIZE_START_TIME = 4 + private const val SIZE_BATTERY = 3 + private const val SIZE_STORAGE = 4 + private const val SIZE_ALARM = 4 + private const val SIZE_AGE = 4 + private const val SIZE_MAGNETO_PLACE = 2 + private const val SIZE_UNUSED_CGM = 5 + private const val SIZE_UNUSED_COMMAND_CONFIRM = 2 + private const val SIZE_UNUSED_AUTO_STATUS = 2 + private const val SIZE_UNUSED_LEGACY = 2 } + val maskHandlers: Map Int> = mapOf( + MASK_SUSPEND to ::handleSuspend, + MASK_NORMAL_BOLUS to ::handleNormalBolus, + MASK_EXTENDED_BOLUS to ::handleExtendedBolus, + MASK_BASAL to ::handleBasal, + MASK_SETUP to ::handleSetup, + MASK_RESERVOIR to ::handleReservoir, + MASK_START_TIME to ::handleStartTime, + MASK_BATTERY to ::handleBattery, + MASK_STORAGE to ::handleStorage, + MASK_ALARM to ::handleAlarm, + MASK_AGE to ::handleAge, + MASK_MAGNETO_PLACE to ::handleUnknown1, + MASK_UNUSED_CGM to ::handleUnusedCGM, + MASK_UNUSED_COMMAND_CONFIRM to ::handleUnusedCommandConfirm, + MASK_UNUSED_AUTO_STATUS to ::handleUnusedAutoStatus, + MASK_UNUSED_LEGACY to ::handleUnusedLegacy + ) + + var newPatchStartTime = 0L + init { injector.androidInjector().inject(this) } @@ -74,7 +113,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { medtrumPump.pumpState = state } - if (notification.size > NOTIF_STATE_END) { + if (notification.size > NOTIF_STATE_END + SIZE_FIELD_MASK) { handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size)) } } @@ -82,164 +121,219 @@ class NotificationPacket(val injector: HasAndroidInjector) { /** * Handle a message with a field mask, can be used by other packets as well */ - fun handleMaskedMessage(data: ByteArray) { + fun handleMaskedMessage(data: ByteArray): Boolean { val fieldMask = data.copyOfRange(0, 2).toInt() var offset = 2 - var newPatchStartTime: Long? = null + + val expectedLength = calculateExpectedLengthBasedOnFieldMask(fieldMask) + if (data.size < expectedLength) { + aapsLogger.error(LTag.PUMPCOMM, "Incorrect message length. Expected at least $expectedLength bytes.") + return false + } aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask") - if (fieldMask and MASK_SUSPEND != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received") - medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) - aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}") - offset += 4 - } - - if (fieldMask and MASK_NORMAL_BOLUS != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") - val bolusData = data.copyOfRange(offset, offset + 1).toInt() - val bolusType = bolusData and 0x7F - val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0 - val bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 - aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered") - medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered) - offset += 3 - } - - if (fieldMask and MASK_EXTENDED_BOLUS != 0) { - aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!") - offset += 3 - } - - if (fieldMask and MASK_BASAL != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") - val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] - val basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt() - val basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() - val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong()) - val basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() - val basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 - val basalDelivery = (basalRateAndDelivery shr 12) * 0.05 - aapsLogger.debug( - LTag.PUMPCOMM, - "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery" - ) - // Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set - if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) { - medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) + for ((mask, handler) in maskHandlers) { + if (fieldMask and mask != 0) { + offset = handler(data, offset) } - offset += 12 } - if (fieldMask and MASK_SETUP != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received") - medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt() - aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}") - offset += 1 - } + return true + } - if (fieldMask and MASK_RESERVOIR != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") - medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05 - aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}") - offset += 2 - } + private fun calculateExpectedLengthBasedOnFieldMask(fieldMask: Int): Int { + var expectedLength = SIZE_FIELD_MASK - if (fieldMask and MASK_START_TIME != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") - newPatchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) - if (medtrumPump.patchStartTime != newPatchStartTime) { - aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $newPatchStartTime") - medtrumPump.patchStartTime = newPatchStartTime + val sizeMap = mapOf( + MASK_SUSPEND to SIZE_SUSPEND, + MASK_NORMAL_BOLUS to SIZE_NORMAL_BOLUS, + MASK_EXTENDED_BOLUS to SIZE_EXTENDED_BOLUS, + MASK_BASAL to SIZE_BASAL, + MASK_SETUP to SIZE_SETUP, + MASK_RESERVOIR to SIZE_RESERVOIR, + MASK_START_TIME to SIZE_START_TIME, + MASK_BATTERY to SIZE_BATTERY, + MASK_STORAGE to SIZE_STORAGE, + MASK_ALARM to SIZE_ALARM, + MASK_AGE to SIZE_AGE, + MASK_MAGNETO_PLACE to SIZE_MAGNETO_PLACE, + MASK_UNUSED_CGM to SIZE_UNUSED_CGM, + MASK_UNUSED_COMMAND_CONFIRM to SIZE_UNUSED_COMMAND_CONFIRM, + MASK_UNUSED_AUTO_STATUS to SIZE_UNUSED_AUTO_STATUS, + MASK_UNUSED_LEGACY to SIZE_UNUSED_LEGACY + ) + + for ((mask, size) in sizeMap) { + if (fieldMask and mask != 0) { + expectedLength += size } - aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: $newPatchStartTime") - offset += 4 } - if (fieldMask and MASK_BATTERY != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received") - val parameter = data.copyOfRange(offset, offset + 3).toInt() - // Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64 - medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0 - medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0 - aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}") - offset += 3 - } + return expectedLength + } - if (fieldMask and MASK_STORAGE != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") - val sequence = data.copyOfRange(offset, offset + 2).toInt() - if (sequence > medtrumPump.currentSequenceNumber) { - medtrumPump.currentSequenceNumber = sequence + private fun handleSuspend(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Suspend notification received") + medtrumPump.suspendTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) + aapsLogger.debug(LTag.PUMPCOMM, "Suspend time: ${medtrumPump.suspendTime}") + return offset + SIZE_SUSPEND + } + + private fun handleNormalBolus(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") + val bolusData = data.copyOfRange(offset, offset + 1).toInt() + val bolusType = bolusData and 0x7F + val bolusCompleted: Boolean = ((bolusData shr 7) and 0x01) != 0 + val bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 + aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered") + medtrumPump.handleBolusStatusUpdate(bolusType, bolusCompleted, bolusDelivered) + return offset + SIZE_NORMAL_BOLUS + } + + private fun handleExtendedBolus(data: ByteArray, offset: Int): Int { + aapsLogger.error(LTag.PUMPCOMM, "Extended bolus notification received, extended bolus not supported!") + aapsLogger.debug(LTag.PUMPCOMM, "Extended bolus data: ${data.copyOfRange(offset, offset + SIZE_EXTENDED_BOLUS).toLong()}") + return offset + SIZE_EXTENDED_BOLUS + } + + private fun handleBasal(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") + val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] + val basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt() + val basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() + val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong()) + val basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() + val basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 + val basalDelivery = (basalRateAndDelivery shr 12) * 0.05 + aapsLogger.debug( + LTag.PUMPCOMM, + "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalStartTime, basal rate: $basalRate, basal delivery: $basalDelivery" + ) + // Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set + if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) { + medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) + } + return offset + SIZE_BASAL + } + + private fun handleSetup(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Setup notification received") + medtrumPump.primeProgress = data.copyOfRange(offset, offset + 1).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Prime progress: ${medtrumPump.primeProgress}") + return offset + SIZE_SETUP + } + + private fun handleReservoir(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Reservoir notification received") + medtrumPump.reservoir = data.copyOfRange(offset, offset + 2).toInt() * 0.05 + aapsLogger.debug(LTag.PUMPCOMM, "Reservoir: ${medtrumPump.reservoir}") + return offset + SIZE_RESERVOIR + } + + private fun handleStartTime(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") + newPatchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) + if (medtrumPump.patchStartTime != newPatchStartTime) { + aapsLogger.debug(LTag.PUMPCOMM, "Patch start time changed from ${medtrumPump.patchStartTime} to $newPatchStartTime") + medtrumPump.patchStartTime = newPatchStartTime + } + aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: $newPatchStartTime") + return offset + SIZE_START_TIME + } + + private fun handleBattery(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Battery notification received") + val parameter = data.copyOfRange(offset, offset + 3).toInt() + // Precision for voltage A is a guess, voltage B is the important one, threshold: < 2.64 + medtrumPump.batteryVoltage_A = (parameter and 0xFFF) / 512.0 + medtrumPump.batteryVoltage_B = (parameter shr 12) / 512.0 + aapsLogger.debug(LTag.PUMPCOMM, "Battery voltage A: ${medtrumPump.batteryVoltage_A}, battery voltage B: ${medtrumPump.batteryVoltage_B}") + return offset + SIZE_BATTERY + } + + private fun handleStorage(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") + val sequence = data.copyOfRange(offset, offset + 2).toInt() + if (sequence > medtrumPump.currentSequenceNumber) { + medtrumPump.currentSequenceNumber = sequence + } + val patchId = data.copyOfRange(offset + 2, offset + 4).toLong() + if (patchId != medtrumPump.patchId) { + aapsLogger.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!") + if (newPatchStartTime != 0L) { + // This is a fallback for when the activate packet did not receive the ack but the patch activated anyway + aapsLogger.error(LTag.PUMPCOMM, "handleMaskedMessage: Also Received start time in this packet, registering new patch id: $patchId") + medtrumPump.handleNewPatch(patchId, sequence, newPatchStartTime) } - val patchId = data.copyOfRange(offset + 2, offset + 4).toLong() - if (patchId != medtrumPump.patchId) { - aapsLogger.warn(LTag.PUMPCOMM, "handleMaskedMessage: We got wrong patch id!") - if (newPatchStartTime != null) { - // This is a fallback for when the activate packet did not receive the ack but the patch activated anyway - aapsLogger.error(LTag.PUMPCOMM, "handleMaskedMessage: Also Received start time in this packet, registering new patch id: $patchId") - medtrumPump.handleNewPatch(patchId, sequence, newPatchStartTime) - } - } - aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}") - offset += 4 } + aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}") + return offset + SIZE_STORAGE + } - if (fieldMask and MASK_ALARM != 0) { - val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() - val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() - aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter") + private fun handleAlarm(data: ByteArray, offset: Int): Int { + val alarmFlags = data.copyOfRange(offset, offset + 2).toInt() + val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter") - // If no alarm, clear activeAlarm list - if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) { - medtrumPump.clearAlarmState() - } else if (alarmFlags != 0) { - // Check each alarm bit - for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state - val alarmState = AlarmState.values()[i] - if ((alarmFlags shr i) and 1 != 0) { - // If the alarm bit is set, add the corresponding alarm to activeAlarms - if (!medtrumPump.activeAlarms.contains(alarmState)) { - aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms") - medtrumPump.addAlarm(alarmState) - medtrumPump.pumpWarning = alarmState - } - } else if (medtrumPump.activeAlarms.contains(alarmState)) { - // If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it - medtrumPump.removeAlarm(alarmState) + // If no alarm, clear activeAlarm list + if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) { + medtrumPump.clearAlarmState() + } else if (alarmFlags != 0) { + // Check each alarm bit + for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state + val alarmState = AlarmState.values()[i] + if ((alarmFlags shr i) and 1 != 0) { + // If the alarm bit is set, add the corresponding alarm to activeAlarms + if (!medtrumPump.activeAlarms.contains(alarmState)) { + aapsLogger.debug(LTag.PUMPCOMM, "Adding alarm $alarmState to active alarms") + medtrumPump.addAlarm(alarmState) + medtrumPump.pumpWarning = alarmState } + } else if (medtrumPump.activeAlarms.contains(alarmState)) { + // If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it + medtrumPump.removeAlarm(alarmState) } } - offset += 4 } + return offset + SIZE_ALARM + } - if (fieldMask and MASK_AGE != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") - medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() - aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") - offset += 4 - } + private fun handleAge(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Age notification received") + medtrumPump.patchAge = data.copyOfRange(offset, offset + 4).toLong() + aapsLogger.debug(LTag.PUMPCOMM, "Patch age: ${medtrumPump.patchAge}") + return offset + SIZE_AGE + } - if (fieldMask and MASK_UNKNOWN_1 != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unknown 1 notification received, not handled!") - } + private fun handleUnknown1(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement notification received!") + val magnetoPlacement = data.copyOfRange(offset, offset + 2).toInt() + aapsLogger.debug(LTag.PUMPCOMM, "Magneto placement: $magnetoPlacement") + return offset + SIZE_MAGNETO_PLACE + } - if (fieldMask and MASK_UNUSED_CGM != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") - } + private fun handleUnusedCGM(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused CGM data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_CGM).toLong()}") + return offset + SIZE_UNUSED_CGM + } - if (fieldMask and MASK_UNUSED_COMMAND_CONFIRM != 0) { - // This one is a warning, as this happens we need to know about it, and maybe implement - aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!") - } + private fun handleUnusedCommandConfirm(data: ByteArray, offset: Int): Int { + aapsLogger.warn(LTag.PUMPCOMM, "Unused command confirm notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused command confirm data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_COMMAND_CONFIRM).toLong()}") + return offset + SIZE_UNUSED_COMMAND_CONFIRM + } - if (fieldMask and MASK_UNUSED_AUTO_STATUS != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!") - } + private fun handleUnusedAutoStatus(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused auto status data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_AUTO_STATUS).toLong()}") + return offset + SIZE_UNUSED_AUTO_STATUS + } - if (fieldMask and MASK_UNUSED_LEGACY != 0) { - aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") - } + private fun handleUnusedLegacy(data: ByteArray, offset: Int): Int { + aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") + aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy data: ${data.copyOfRange(offset, offset + SIZE_UNUSED_LEGACY).toLong()}") + return offset + SIZE_UNUSED_LEGACY } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt index 97e8d15222..8d35793052 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt @@ -31,7 +31,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) } override fun handleResponse(data: ByteArray): Boolean { - val success = super.handleResponse(data) + var success = super.handleResponse(data) if (success) { val state = MedtrumPumpState.fromByte(data[RESP_STATE_START]) @@ -63,7 +63,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) } // Let the notification packet handle the rest of the sync data - NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData) + success = NotificationPacket(injector).handleMaskedMessage(fieldMask.toByteArray(2) + syncData) } return success diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt index cc5283b9dd..c2b76b71fa 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/BLEComm.kt @@ -40,7 +40,7 @@ interface BLECommCallback { fun onBLEDisconnected() fun onNotification(notification: ByteArray) fun onIndication(indication: ByteArray) - fun onSendMessageError(reason: String) + fun onSendMessageError(reason: String, isRetryAble: Boolean) } @Singleton @@ -258,7 +258,11 @@ class BLEComm @Inject internal constructor( mReadPacket?.addData(value) } if (mReadPacket?.allDataReceived() == true) { - mReadPacket?.getData()?.let { mCallback?.onIndication(it) } + if (mReadPacket?.failed() == true) { + mCallback?.onSendMessageError("ReadDataPacket failed", false) + } else { + mReadPacket?.getData()?.let { mCallback?.onIndication(it) } + } mReadPacket = null } } @@ -279,7 +283,7 @@ class BLEComm @Inject internal constructor( } } } else { - mCallback?.onSendMessageError("onCharacteristicWrite failure") + mCallback?.onSendMessageError("onCharacteristicWrite failure", true) } } @@ -404,7 +408,7 @@ class BLEComm @Inject internal constructor( writeCharacteristic(uartWriteBTGattChar, value) } else { aapsLogger.error(LTag.PUMPBTCOMM, "sendMessage error in writePacket!") - mCallback?.onSendMessageError("error in writePacket!") + mCallback?.onSendMessageError("error in writePacket!", false) } } @@ -430,7 +434,7 @@ class BLEComm @Inject internal constructor( aapsLogger.debug(LTag.PUMPBTCOMM, "writeCharacteristic: ${Arrays.toString(data)}") val success = mBluetoothGatt?.writeCharacteristic(characteristic) if (success != true) { - mCallback?.onSendMessageError("Failed to write characteristic") + mCallback?.onSendMessageError("Failed to write characteristic", true) } } }, WRITE_DELAY_MILLIS) diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt index c04f5e9f1d..ae596414e9 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt @@ -336,21 +336,22 @@ class MedtrumService : DaggerService(), BLECommCallback { if (!canSetBolus()) return false val insulin = detailedBolusInfo.insulin + medtrumPump.bolusDone = false + medtrumPump.bolusStopped = false if (!sendBolusCommand(insulin)) { aapsLogger.error(LTag.PUMPCOMM, "Failed to set bolus") - commandQueue.loadEvents(null) // make sure if anything is delivered (which is highly unlikely at this point) we get it + commandQueue.readStatus(rh.gs(R.string.bolus_error), null) // make sure if anything is delivered (which is highly unlikely at this point) we get it + medtrumPump.bolusDone = true t.insulin = 0.0 return false } val bolusStart = System.currentTimeMillis() - medtrumPump.bolusDone = false - medtrumPump.bolusingTreatment = t - medtrumPump.bolusAmountToBeDelivered = insulin - medtrumPump.bolusStopped = false medtrumPump.bolusProgressLastTimeStamp = bolusStart medtrumPump.bolusStartTime = bolusStart + medtrumPump.bolusingTreatment = t + medtrumPump.bolusAmountToBeDelivered = insulin detailedBolusInfo.timestamp = bolusStart // Make sure the timestamp is set to the start of the bolus detailedBolusInfoStorage.add(detailedBolusInfo) // will be picked up on reading history @@ -735,9 +736,9 @@ class MedtrumService : DaggerService(), BLECommCallback { currentState.onIndication(indication) } - override fun onSendMessageError(reason: String) { + override fun onSendMessageError(reason: String, isRetryAble: Boolean) { aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason") - currentState.onSendMessageError(reason) + currentState.onSendMessageError(reason, isRetryAble) } /** Service stuff */ @@ -822,10 +823,10 @@ class MedtrumService : DaggerService(), BLECommCallback { return responseSuccess } - fun onSendMessageError(reason: String) { + fun onSendMessageError(reason: String, isRetryAble: Boolean) { aapsLogger.warn(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason") // Retry 3 times - if (sendRetryCounter < 3) { + if (sendRetryCounter < 3 && isRetryAble) { sendRetryCounter++ mPacket?.getRequest()?.let { bleComm.sendMessage(it) } } else { diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt new file mode 100644 index 0000000000..211f35da72 --- /dev/null +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/CrcUtil.kt @@ -0,0 +1,13 @@ +object CrcUtils { + + private val lookupTable: UByteArray = ubyteArrayOf(0u, 155u, 173u, 54u, 193u, 90u, 108u, 247u, 25u, 130u, 180u, 47u, 216u, 67u, 117u, 238u, 50u, 169u, 159u, 4u, 243u, 104u, 94u, 197u, 43u, 176u, 134u, 29u, 234u, 113u, 71u, 220u, 100u, 255u, 201u, 82u, 165u, 62u, 8u, 147u, 125u, 230u, 208u, 75u, 188u, 39u, 17u, 138u, 86u, 205u, 251u, 96u, 151u, 12u, 58u, 161u, 79u, 212u, 226u, 121u, 142u, 21u, 35u, 184u, 200u, 83u, 101u, 254u, 9u, 146u, 164u, 63u, 209u, 74u, 124u, 231u, 16u, 139u, 189u, 38u, 250u, 97u, 87u, 204u, 59u, 160u, 150u, 13u, 227u, 120u, 78u, 213u, 34u, 185u, 143u, 20u, 172u, 55u, 1u, 154u, 109u, 246u, 192u, 91u, 181u, 46u, 24u, 131u, 116u, 239u, 217u, 66u, 158u, 5u, 51u, 168u, 95u, 196u, 242u, 105u, 135u, 28u, 42u, 177u, 70u, 221u, 235u, 112u, 11u, 144u, 166u, 61u, 202u, 81u, 103u, 252u, 18u, 137u, 191u, 36u, 211u, 72u, 126u, 229u, 57u, 162u, 148u, 15u, 248u, 99u, 85u, 206u, 32u, 187u, 141u, 22u, 225u, 122u, 76u, 215u, 111u, 244u, 194u, 89u, 174u, 53u, 3u, 152u, 118u, 237u, 219u, 64u, 183u, 44u, 26u, 129u, 93u, 198u, 240u, 107u, 156u, 7u, 49u, 170u, 68u, 223u, 233u, 114u, 133u, 30u, 40u, 179u, 195u, 88u, 110u, 245u, 2u, 153u, 175u, 52u, 218u, 65u, 119u, 236u, 27u, 128u, 182u, 45u, 241u, 106u, 92u, 199u, 48u, 171u, 157u, 6u, 232u, 115u, 69u, 222u, 41u, 178u, 132u, 31u, 167u, 60u, 10u, 145u, 102u, 253u, 203u, 80u, 190u, 37u, 19u, 136u, 127u, 228u, 210u, 73u, 149u, 14u, 56u, 163u, 84u, 207u, 249u, 98u, 140u, 23u, 33u, 186u, 77u, 214u, 224u, 123u) + + fun calcCrc8(value: ByteArray, size: Int): Byte { + var crc8: UByte = 0u + for (i in 0 until size) { + val tableIndex: UByte = (value[i].toUByte() xor crc8) + crc8 = lookupTable[tableIndex.toInt()] + } + return crc8.toByte() + } +} diff --git a/pump/medtrum/src/main/res/values-cs-rCZ/strings.xml b/pump/medtrum/src/main/res/values-cs-rCZ/strings.xml index 3bd036a325..1493204dbf 100644 --- a/pump/medtrum/src/main/res/values-cs-rCZ/strings.xml +++ b/pump/medtrum/src/main/res/values-cs-rCZ/strings.xml @@ -53,6 +53,7 @@ Vybitá baterie Žádná kalibrace Nepodařilo se aktualizovat časové pásmo pumpy, odložit zprávu a aktualizovat ručně. + Chyba bolusu Opakovat Další @@ -101,6 +102,8 @@ Stiskněte Další pro obnovení aktivace nebo Zahodit pro resetování stavu aktivace. Počkejte prosím, čtení stavu aktivace z Patche. + Výstraha při nedostupné pumpě byla vynucena, protože patch pumpa Medtrum může selhat a být nedostupná. + Doporučujeme nastavit na 30 minut, protože patch pumpa Medtrum může selhat a být nedostupná. Sériové číslo Zadejte sériové číslo základny Patche. Neplatné sériové číslo! diff --git a/pump/medtrum/src/main/res/values-es-rES/strings.xml b/pump/medtrum/src/main/res/values-es-rES/strings.xml index 50534174b7..c8c013cc15 100644 --- a/pump/medtrum/src/main/res/values-es-rES/strings.xml +++ b/pump/medtrum/src/main/res/values-es-rES/strings.xml @@ -53,6 +53,7 @@ Batería agotada Sin calibración Error al actualizar la zona horaria de la bomba, posponer zumbido y actualizar manualmente. + Error de bolo Reintentar Siguiente @@ -101,6 +102,8 @@ Presiona Siguiente para reanudar la activación o Descartar para restablecer el estado de activación. Por favor, espera, leyendo el estado de activación de la bomba. + Se ha habilitado la alerta de \"Inaccesible forzada\" debido a la posibilidad de que el parche de Medtrum falle y no pueda ser contactado. + Se recomienda configurarlo a 30 minutos, debido a que el parche de Medtrum puede presentar fallos y quedar inaccesible. Número de serie Introduce el número de serie de la base de la bomba. ¡Número de serie inválido! diff --git a/pump/medtrum/src/main/res/values-nb-rNO/strings.xml b/pump/medtrum/src/main/res/values-nb-rNO/strings.xml index b2f4013796..8100bc66f2 100644 --- a/pump/medtrum/src/main/res/values-nb-rNO/strings.xml +++ b/pump/medtrum/src/main/res/values-nb-rNO/strings.xml @@ -21,7 +21,7 @@ %.2f E %.2f V Basal type - Basalrate + Basaldose %.2f E/t Pumpetype FW versjon @@ -53,6 +53,7 @@ Batteri er tatt ut Ingen kalibrering Kunne ikke oppdatere tidssone på pumpen. Bekreft meldingen og oppdater manuelt. + Bolus feilet Prøv igjen Neste @@ -84,7 +85,7 @@ Trykk Neste for å fortsette. Trykk Neste for å starte aktivering. Fjern sikkerhetslåsen. Koble pumpen til kroppen. Trykk nål-knappen. - Aktiverer pumpe og setter basalrate. Vennligst vent. + Aktiverer pumpe og setter basaldose. Vennligst vent. Kunne ikke aktivere, trykk Prøv igjen for å prøve igjen. Ny patch aktivert. %.2f enheter gjenstår. Trykk OK for å gå tilbake til hovedskjermen. @@ -101,6 +102,8 @@ Trykk Neste for å gjenoppta aktiveringen eller Forkast for å tilbakestille aktiveringsstatusen. Vennligst vent, leser aktiveringsstatus fra pumpen. + Varsel om pumpe utilgjengelig tvinges aktivert, fordi Medtrum-patching kan mislykkes og bli utilgjengelig. + Anbefales å sette til 30 minutter, fordi Medtrum-patching kan mislykkes og bli utilgjengelig. Serienummer Skriv inn serienummeret til pumpens base. Ugyldig serienummer! diff --git a/pump/medtrum/src/main/res/values-nl-rNL/strings.xml b/pump/medtrum/src/main/res/values-nl-rNL/strings.xml index a55cf9a280..c566589058 100644 --- a/pump/medtrum/src/main/res/values-nl-rNL/strings.xml +++ b/pump/medtrum/src/main/res/values-nl-rNL/strings.xml @@ -53,6 +53,7 @@ Batterij leeg Geen kalibratie Update van pomp tijdzone mislukt, snooze bericht en vernieuw handmatig. + Bolus fout Opnieuw Volgende @@ -101,6 +102,8 @@ Druk op Volgende om de activering te hervatten of Verwijderen om de activeringsstatus te resetten. Een ogenblik geduld, activeringsstatus van de pomp wordt gelezen. + Waarschuwing onbereikbare pump geforceerd ingeschakeld, omdat de Medtrum-patch kan falen en onbereikbaar kan zijn. + Geadviseerd om op 30 minuten in te stellen, omdat de Medtrum-patch kan falen en onbereikbaar kan zijn. Serienummer Voer het serienummer van uw pompbasis in. Ongeldig serienummer! @@ -108,6 +111,7 @@ Alarminstellingen Selecteer uw gewenste alarminstellingen voor de pomp. Notificatie bij pomp waarschuwing + Melding weergeven op niet-kritische pompwaarschuwingen: batterij bijna leeg, reservoir bijna leeg (20 eenheden) en vervalt bijna. Aanbevolen om ingeschakeld te laten wanneer pomp alarmen op stil zijn ingesteld. Patch vervalt Wanneer ingeschakeld, zal de patch na 3 dagen verlopen met een extra periode van 8 uur coulance. Maximale insuline per uur diff --git a/pump/medtrum/src/main/res/values-ro-rRO/strings.xml b/pump/medtrum/src/main/res/values-ro-rRO/strings.xml index 1b55a6cc25..01b5297304 100644 --- a/pump/medtrum/src/main/res/values-ro-rRO/strings.xml +++ b/pump/medtrum/src/main/res/values-ro-rRO/strings.xml @@ -53,6 +53,7 @@ Baterie descărcată Fără calibrare Actualizarea fusului orar al pompei a eșuat, amână mesajul și actualizează manual. + Eroare bolus Încearcă din nou Înainte @@ -101,6 +102,8 @@ Apasă Înainte pentru a relua activarea sau Renunțare pentru a reseta statusul activării. Te rog, așteaptă. Se citește starea de activare din pompă. + Alertă de inaccesibilitate activată forțat, deoarece patchul Medtrum poate eșua și să nu fie accesibil. + Recomandat să se seteze la 30 de minute, deoarece patchul Medtrum poate eșua și să nu fie accesibil. Număr de serie Introdu numărul de serie al bazei pompei. Număr de serie invalid! diff --git a/pump/medtrum/src/main/res/values-ru-rRU/strings.xml b/pump/medtrum/src/main/res/values-ru-rRU/strings.xml index 533755408c..c029351280 100644 --- a/pump/medtrum/src/main/res/values-ru-rRU/strings.xml +++ b/pump/medtrum/src/main/res/values-ru-rRU/strings.xml @@ -6,6 +6,7 @@ Интеграция с помпами Medtrum Nano и Medtrum 300U Настройки Medtrum Ошибка помпы: %1$s !! + Помпа - Предупреждение: %1$s Помпа приостановлена Помпа приостановлена из-за превышения максимального количества инсулина в час Помпа приостановлена в связи с превышением максимального допустимого количества инсулина в сутки @@ -52,6 +53,7 @@ Батарея разряжена Нет калибровки Не удалось обновить часовой пояс помпы, закройте сообщение и обновите вручную. + Болюс - ошибка Повторить Далее @@ -100,12 +102,16 @@ Нажмите Далее, чтобы возобновить активацию или Discard для сброса статуса активации. Пожалуйста, подождите, получение статуса активации с помпы. + Принудительно включена сигнализация о недоступности помпы, так как патч Medtrum может выйти из строя и быть недоступным. + Рекомендуется установить значение в 30 минут, так как патч Medtrum может выйти из строя и быть недоступным. Серийный номер Введите серийный номер основания вашей помпы. Неверный серийный номер! Помпа не тестировалась: %1$d! Свяжитесь с нами в discord или github Настройки оповещений Выберите предпочитаемые параметры оповещений помпы. + Уведомление об оповещениях помпы + Показывать уведомления о некритических оповещениях помпы: низком заряде батареи, низком запасе инсулина (20 единиц) и приближающемся истечении срока работы. Рекомендуется оставить включенным, когда звук оповещений выключен. Окончание срока действия патча Если включено, то патч закончит свое действие через 3 дня, с дополнительными 8 часами после этого. Максимальное количество инсулина в час diff --git a/pump/medtrum/src/main/res/values-sk-rSK/strings.xml b/pump/medtrum/src/main/res/values-sk-rSK/strings.xml index 45ced610f5..a03c3f0d9a 100644 --- a/pump/medtrum/src/main/res/values-sk-rSK/strings.xml +++ b/pump/medtrum/src/main/res/values-sk-rSK/strings.xml @@ -6,6 +6,7 @@ Integrácia pumpy Medtrum Nano a Medtrum 300U Nastavenie pumpy Medtrum Chyba pumpy: %1$s!! + Výstraha pumpy: %1$s Pumpa je pozastavená Pumpa je pozastavená kvôli prekročeniu maximálneho množstva inzulínu za hodinu Pumpa je pozastavená kvôli prekročeniu maximálneho množstva inzulínu za deň @@ -52,6 +53,7 @@ Vybitá batéria Žiadna kalibrácia Nepodarilo sa aktualizovať časové pásmo pumpy, odložiť správu a aktualizovať ručne. + Chyba bolusu Opakovať Ďalšia @@ -100,12 +102,16 @@ Stlačte Ďalšia pre obnovenie aktivácie alebo Zahodiť pre resetovanie stavu aktivácie. Počkajte prosím, načítanie stavu aktivácie z Patch. + Výstraha pri nedostupnej pumpe bola vynutená, pretože patch pumpa Medtrum môže zlyhať a byť nedostupná. + Doporučujeme nastaviť na 30 minút, protože patch pumpa Medtrum môže zlyhať a byť nedostupná. Sériové číslo Zadajte sériové číslo základne Patch. Neplatné sériové číslo! Nevyskúšaná pumpa: %1$d! Kontaktujte nás na Discorde alebo Githube, kde získate podporu. Nastavenie výstrah Vyberte preferované nastavenie výstrah. + Oznámenie o výstrahe pumpy + Zobraziť upozornenie na nie kritické varovanie pumpy: vybitá batéria, takmer prázdny zásobník (20 jednotiek) a čoskoro dojde inzulín. Doporučené ponechať zapnuté, keď sú výstrahy pumpy nastavené na ticho. Expirácia Patch Pokiaľ je povolené, Patch expiruje po 3 dňoch s maximálnou dobou odkladu 8 hodín. Hodinové maximum inzulínu diff --git a/pump/medtrum/src/main/res/values-tr-rTR/strings.xml b/pump/medtrum/src/main/res/values-tr-rTR/strings.xml index 5ea69fde22..143f6dab14 100644 --- a/pump/medtrum/src/main/res/values-tr-rTR/strings.xml +++ b/pump/medtrum/src/main/res/values-tr-rTR/strings.xml @@ -6,6 +6,7 @@ Medtrum Nano ve Medtrum 300U için pompa entegrasyonu Medtrum pompa ayarları Pompa hatası: %1$s !! + Pompa uyarısı: %1$s Pompa durduruldu Saatlik maksimum insülinin aşılması nedeniyle pompa askıya alındı Günlük maksimum insülinin aşılması nedeniyle pompa askıya alındı @@ -52,6 +53,7 @@ Pil bitti Kalibrasyon yok Pompa saat dilimi güncellenemedi, mesaj ertelendi ve manuel olarak yenilendi. + Bolus hatası Tekrar dene İleri @@ -100,12 +102,16 @@ Etkinleştirmeyi sürdürmek için İleri\'ye veya etkinleştirmeyi iptal etmek için Çıkar\'a basın. Lütfen bekleyin, pompadan aktivasyon durumu okunuyor. + Medtrum yaması başarısız olabileceği ve erişilemeyebileceği için ulaşılamıyor uyarısı zorunlu olarak etkinleştirildi. + Medtrum yaması başarısız olabileceği ve erişilemeyebileceği için 30 dakikaya ayarlanması önerilir. Seri Numarası Pompa tabanınızın seri numarasını girin. Geçersiz seri numarası! Pompa test edilmedi: %1$d! Destek için lütfen discord veya github üzerinden bizimle iletişime geçin Alarm Ayarları Tercih ettiğiniz pompa alarm ayarlarını seçin. + Pompa uyarısı hakkında bildirim + Kritik olmayan pompa uyarılarıyla ilgili bildirimi göster: düşük pil, düşük rezervuar (20 birim) ve yakında süresi doluyor. Pompa alarmları sessize ayarlandığında etkin bırakılması önerilir. Patch süre sonu Etkinleştirildiğinde, patch 3 gün sonra sona erecek ve bunun ardından 8 saatlik bir ek süre tanınacaktır. Saatlik Maksimum insülin diff --git a/pump/medtrum/src/main/res/values/strings.xml b/pump/medtrum/src/main/res/values/strings.xml index b508b7150d..01bbe48505 100644 --- a/pump/medtrum/src/main/res/values/strings.xml +++ b/pump/medtrum/src/main/res/values/strings.xml @@ -80,6 +80,7 @@ Battery out No calibration Failed to update pump timezone, snooze message and refresh manually. + Bolus error Retry @@ -134,6 +135,8 @@ Please wait, reading activation status from pump. + Unreachable alert forced enabled, because Medtrum patch can fail and be unreachable. + Advised to set to 30 minutes, because Medtrum patch can fail and be unreachable. Serial Number Enter the serial number of your pump base. Invalid serial number! diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt new file mode 100644 index 0000000000..9b61869cff --- /dev/null +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/ReadDataPacketTest.kt @@ -0,0 +1,154 @@ +package info.nightscout.pump.medtrum.comm + +import com.google.common.truth.Truth.assertThat +import org.junit.jupiter.api.Test + +class ReadDataPacketTest { + + @Test + fun givenCorrectBytesExpectPacketNotFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.getData()).isEqualTo( + byteArrayOf( + 51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 0, 0, 0, -80, + -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 19, -82 + ) + ) + assertThat(packet.failed()).isFalse() + } + + @Test + fun givenIncorrectCRCInFirstChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -1, -62, -1, -1, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInSecondChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -1, 84, 18, 10, 0, 10, -1, -1, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInThirdChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectCRCInLastChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk3 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk4 = byteArrayOf(51, 99, 10, -1, -1, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenIncorrectSequenceExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(51, 99, 10, 1, 0, 0, -86, 44, 1, -1, -85, 21, -108, -62, 1, 0, 22, 0, 1, 75) + val chunk2 = byteArrayOf(51, 99, 10, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -61, -59, -120, 5) + val chunk3 = byteArrayOf(51, 99, 10, 2, 0, 0, 0, -80, -116, 84, 18, 10, 0, 10, 0, 0, 0, 0, 0, -10) + val chunk4 = byteArrayOf(51, 99, 10, 4, 19, -82, -80) + + // act + val packet = ReadDataPacket(chunk1) + packet.addData(chunk2) + packet.addData(chunk3) + packet.addData(chunk4) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } + + @Test + fun givenCorrectBytesOneChunkExpectPacketNotFailed() { + // arrange + val chunk1 = byteArrayOf(14, 5, 0, 0, 0, 0, 2, 80, 1, 74, 64, 4, 0, -16, 0) + + // act + val packet = ReadDataPacket(chunk1) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.getData()).isEqualTo(byteArrayOf(14, 5, 0, 0, 0, 0, 2, 80, 1, 74, 64, 4, 0, -16)) + assertThat(packet.failed()).isFalse() + } + + @Test + fun givenIncorrectBytesOneChunkExpectPacketFailed() { + // arrange + val chunk1 = byteArrayOf(14, 5, 0, -1, -1, -1, 2, 80, 1, 74, 64, 4, 0, -16, 0) + + // act + val packet = ReadDataPacket(chunk1) + + // assert + assertThat(packet.allDataReceived()).isTrue() + assertThat(packet.failed()).isTrue() + } +} diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt index 90664ee302..106d0aa892 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacketTest.kt @@ -87,4 +87,17 @@ class NotificationPacketTest : MedtrumTestBase() { assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(1.65) assertThat(medtrumPump.reservoir).isWithin(0.01).of(161.95) } + + @Test fun handleNotificationGivenFieldMaskButMessageTooShortThenNothingSaved() { + // Inputs + val data = byteArrayOf(67, 41, 67, -1, 122, 95, 18, 0, 73, 1, 19, 0, 1, 0, 20, 0, 0, 0, 0, 16) + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.suspendTime).isEqualTo(0) + assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) + assertThat(medtrumPump.currentSequenceNumber).isEqualTo(0) + } } diff --git a/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt b/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt index 4bcd349a12..2628c2d0d3 100644 --- a/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt +++ b/pump/omnipod-common/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/common/ui/wizard/common/activity/OmnipodWizardActivityBase.kt @@ -1,6 +1,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.common.activity import android.os.Bundle +import android.view.WindowManager import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AlertDialog import androidx.navigation.NavController @@ -12,6 +13,7 @@ abstract class OmnipodWizardActivityBase : TranslatedDaggerAppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { exitActivityAfterConfirmation() diff --git a/pump/omnipod-common/src/main/res/values-nb-rNO/strings.xml b/pump/omnipod-common/src/main/res/values-nb-rNO/strings.xml index ebd443fd13..6682ce3e14 100644 --- a/pump/omnipod-common/src/main/res/values-nb-rNO/strings.xml +++ b/pump/omnipod-common/src/main/res/values-nb-rNO/strings.xml @@ -106,9 +106,9 @@ Utløpspåminnelse aktivert Når aktivert, vil Pod\'en pipe når tidspunktet er nådd Påminnelse før utløp (72 timer) - Utløpspåminnelse aktivert + Nedstengingspåminnelse aktivert Når aktivert, vil Pod\'en pipe når tidspunktet er nådd og en time før nedstenging - Påminnelse før utløp (80 timer) + Påminnelse før nedstenging (80 timer) Varsel om lavt reservoar aktivert Antall enheter Demp Pod-varsler automatisk diff --git a/pump/omnipod-dash/src/main/res/values-nb-rNO/strings.xml b/pump/omnipod-dash/src/main/res/values-nb-rNO/strings.xml index dffebca5c9..b7b4c84bed 100644 --- a/pump/omnipod-dash/src/main/res/values-nb-rNO/strings.xml +++ b/pump/omnipod-dash/src/main/res/values-nb-rNO/strings.xml @@ -43,7 +43,7 @@ Profilen ble angitt Pausing av insulintilførsel ble ikke bekreftet! Vennligst oppdater Pod-status fra Omnipod-fanen og gjenoppta insulinlevering om nødvendig. Insulintilførsel er pauset - Tidssone på pod er forskjellig fra tidssonen på telefon. Basalrater er feil. Bytt profil for å korrigere + Tidssone på pod er forskjellig fra tidssonen på telefon. Basaldoser er feil. Bytt profil for å korrigere Kunne ikke sette ny basalprofil. Insulintilførsel er pauset Endring av basalprofil kan ha feilet. Insulinlevering kan bli stoppet! Vennligst velg Oppdater Pod fra Omnipod-fanen og velg gjenoppta levering hvis nødvendig. Status for levering av bolusdoser er usikker. Oppdater pod-statusen for å verifisere. diff --git a/pump/omnipod-eros/src/main/res/values-nb-rNO/strings.xml b/pump/omnipod-eros/src/main/res/values-nb-rNO/strings.xml index d2277396ce..ba11108eea 100644 --- a/pump/omnipod-eros/src/main/res/values-nb-rNO/strings.xml +++ b/pump/omnipod-eros/src/main/res/values-nb-rNO/strings.xml @@ -52,7 +52,7 @@ Endring av tid mislyktes. Insulinlevering er pauset! Velg gjenoppta levering manuelt fra Omnipod-fanen. Feilet i å lese Pulsloggen Feilet i forsøket på å endre tid på Pod. Du må gå inn i Omnipod menyen og manuelt synkronisere tiden. - Operasjon ikke mulig.\n\n Du må konfigurere Medtronic pumpen før du kan bruke denne funksjonen. + Operasjon ikke mulig.\n\nDu må konfigurere Omnipod før du kan bruke denne funksjonen. Kan ikke kontrollere om bolusdosen ble gitt. Kontroller at Pod\'en gir bolus ved å lytte etter klikkk. Dersom du er sikker på at bolusen ikke var vellykket, bør du manuelt slette bolusoppføring fra Behandlingene, selv om du klikker \'Avbryt bolus\' nå! Klarte ikke å bekrefte om SMB bolus (%1$.2f E) ble gitt. Hvis du er helt sikker på at Bolus ikke ble levert, da bør du manuelt slette SMB-oppføringen fra Behandlinger. En midlertidig basal utføres på Pod, men AAPS har ingen informasjon om denne midlertidige basalen. Vennligst avbryt midlertidig basal manuelt. diff --git a/ui/src/main/res/layout/dialog_wizard.xml b/ui/src/main/res/layout/dialog_wizard.xml index 6da6b7c2b3..885d3204f9 100644 --- a/ui/src/main/res/layout/dialog_wizard.xml +++ b/ui/src/main/res/layout/dialog_wizard.xml @@ -38,6 +38,43 @@ + + + + + + + + - - - - - - - - diff --git a/ui/src/main/res/values-nb-rNO/strings.xml b/ui/src/main/res/values-nb-rNO/strings.xml index 09efac2ec8..206e1310d4 100644 --- a/ui/src/main/res/values-nb-rNO/strings.xml +++ b/ui/src/main/res/values-nb-rNO/strings.xml @@ -124,7 +124,7 @@ DPV standardprofil Ugyldig % verdi - Basalrate + Basaldose STOPP er trykket diff --git a/wear/src/main/kotlin/app/aaps/wear/comm/DataHandlerWear.kt b/wear/src/main/kotlin/app/aaps/wear/comm/DataHandlerWear.kt index 119fc92587..038f2124f8 100644 --- a/wear/src/main/kotlin/app/aaps/wear/comm/DataHandlerWear.kt +++ b/wear/src/main/kotlin/app/aaps/wear/comm/DataHandlerWear.kt @@ -186,7 +186,17 @@ class DataHandlerWear @Inject constructor( .subscribe { aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${it.sourceNodeId}") persistence.store(it) - persistence.readCustomWatchface()?.let { + persistence.readSimplifiedCwf()?.let { + rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false))) + } + } + disposable += rxBus + .toObservable(EventData.ActionUpdateCustomWatchface::class.java) + .observeOn(aapsSchedulers.io) + .subscribe { + aapsLogger.debug(LTag.WEAR, "Custom Watchface received from ${it.sourceNodeId}") + persistence.store(it) + persistence.readSimplifiedCwf()?.let { rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, false))) } } @@ -205,7 +215,7 @@ class DataHandlerWear @Inject constructor( .observeOn(aapsSchedulers.io) .subscribe { eventData -> aapsLogger.debug(LTag.WEAR, "Custom Watchface requested from ${eventData.sourceNodeId} export ${eventData.exportFile}") - persistence.readCustomWatchface(eventData.exportFile)?.let { + persistence.readSimplifiedCwf(eventData.exportFile)?.let { rxBus.send(EventWearDataToMobile(EventData.ActionGetCustomWatchface(it, eventData.exportFile))) } } diff --git a/wear/src/main/kotlin/app/aaps/wear/comm/DataLayerListenerServiceWear.kt b/wear/src/main/kotlin/app/aaps/wear/comm/DataLayerListenerServiceWear.kt index 11914555ad..c701b69391 100644 --- a/wear/src/main/kotlin/app/aaps/wear/comm/DataLayerListenerServiceWear.kt +++ b/wear/src/main/kotlin/app/aaps/wear/comm/DataLayerListenerServiceWear.kt @@ -12,6 +12,7 @@ import app.aaps.core.interfaces.rx.bus.RxBus import app.aaps.core.interfaces.rx.events.EventWearDataToMobile import app.aaps.core.interfaces.rx.events.EventWearToMobile import app.aaps.core.interfaces.rx.weardata.EventData +import app.aaps.core.interfaces.rx.weardata.ZipWatchfaceFormat import app.aaps.core.interfaces.sharedPreferences.SP import app.aaps.wear.interaction.utils.Persistence import app.aaps.wear.interaction.utils.WearUtil @@ -127,9 +128,11 @@ class DataLayerListenerServiceWear : WearableListenerService() { } rxDataPath -> { - aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${messageEvent.data}") - val command = EventData.deserializeByte(messageEvent.data) - rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId }) + aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${messageEvent.data.size}") + ZipWatchfaceFormat.loadCustomWatchface(messageEvent.data, "", false)?.let { + val command = EventData.ActionSetCustomWatchface(it.cwfData) + rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId }) + } // Use this sender transcriptionNodeId = messageEvent.sourceNodeId aapsLogger.debug(LTag.WEAR, "Updated node: $transcriptionNodeId") diff --git a/wear/src/main/kotlin/app/aaps/wear/interaction/utils/Persistence.kt b/wear/src/main/kotlin/app/aaps/wear/interaction/utils/Persistence.kt index 67dcf70797..dcf074a80a 100644 --- a/wear/src/main/kotlin/app/aaps/wear/interaction/utils/Persistence.kt +++ b/wear/src/main/kotlin/app/aaps/wear/interaction/utils/Persistence.kt @@ -3,6 +3,9 @@ package app.aaps.wear.interaction.utils import app.aaps.annotations.OpenForTesting import app.aaps.core.interfaces.logging.AAPSLogger import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.rx.events.EventMobileToWear +import app.aaps.core.interfaces.rx.weardata.CwfData +import app.aaps.core.interfaces.rx.weardata.CwfMetadataKey import app.aaps.core.interfaces.rx.weardata.EventData import app.aaps.core.interfaces.rx.weardata.EventData.Companion.deserialize import app.aaps.core.interfaces.rx.weardata.EventData.SingleBg @@ -149,6 +152,26 @@ open class Persistence @Inject constructor( return null } + fun readSimplifiedCwf(isDefault: Boolean = false): EventData.ActionSetCustomWatchface? { + try { + var s = sp.getStringOrNull(if (isDefault) CUSTOM_DEFAULT_WATCHFACE else CUSTOM_WATCHFACE, null) + if (s != null) { + return (deserialize(s) as EventData.ActionSetCustomWatchface).let { + EventData.ActionSetCustomWatchface(it.customWatchfaceData.simplify() ?:it.customWatchfaceData) + } + + } else { + s = sp.getStringOrNull(CUSTOM_DEFAULT_WATCHFACE, null) + if (s != null) { + return deserialize(s) as EventData.ActionSetCustomWatchface + } + } + } catch (exception: Exception) { + aapsLogger.error(LTag.WEAR, exception.toString()) + } + return null + } + fun store(singleBg: SingleBg) { putString(BG_DATA_PERSISTENCE_KEY, singleBg.serialize()) aapsLogger.debug(LTag.WEAR, "Stored BG data: $singleBg") @@ -175,6 +198,21 @@ open class Persistence @Inject constructor( aapsLogger.debug(LTag.WEAR, "Stored Custom Watchface ${customWatchface.customWatchfaceData} ${isdefault}: $customWatchface") } + fun store(customWatchface: EventData.ActionUpdateCustomWatchface) { + readCustomWatchface()?.let { savedCwData -> + if (customWatchface.customWatchfaceData.metadata[CwfMetadataKey.CWF_NAME] == savedCwData.customWatchfaceData.metadata[CwfMetadataKey.CWF_NAME] && + customWatchface.customWatchfaceData.metadata[CwfMetadataKey.CWF_AUTHOR_VERSION] == savedCwData.customWatchfaceData.metadata[CwfMetadataKey.CWF_AUTHOR_VERSION] + ) { + // if same name and author version, then resync metadata to watch to update filename and authorization + val newCwfData = CwfData(savedCwData.customWatchfaceData.json, customWatchface.customWatchfaceData.metadata, savedCwData.customWatchfaceData.resDatas) + EventData.ActionSetCustomWatchface(newCwfData).also { + putString(CUSTOM_WATCHFACE, it.serialize()) + aapsLogger.debug(LTag.WEAR, "Update Custom Watchface ${it.customWatchfaceData} : $customWatchface") + } + } + } +} + fun setDefaultWatchface() { readCustomWatchface(true)?.let { store(it) } aapsLogger.debug(LTag.WEAR, "Custom Watchface reset to default") diff --git a/wear/src/main/kotlin/app/aaps/wear/watchfaces/CustomWatchface.kt b/wear/src/main/kotlin/app/aaps/wear/watchfaces/CustomWatchface.kt index 527761d133..392b27f4d4 100644 --- a/wear/src/main/kotlin/app/aaps/wear/watchfaces/CustomWatchface.kt +++ b/wear/src/main/kotlin/app/aaps/wear/watchfaces/CustomWatchface.kt @@ -513,12 +513,12 @@ class CustomWatchface : BaseWatchFace() { if (viewJson.has(TEXTVALUE.key)) view.text = viewJson.optString(TEXTVALUE.key) (dynData?.getDrawable() ?: textDrawable())?.let { - if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files - it.colorFilter = cwf.changeDrawableColor(dynData?.getFontColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) + if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) not for svg files + it.colorFilter = cwf.changeDrawableColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) else it.clearColorFilter() view.background = it - } ?: apply { + } ?: apply { // if no drawable loaded either background key or dynData, then apply color to text background view.setBackgroundColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key, TRANSPARENT), Color.TRANSPARENT)) } } ?: apply { view.text = "" } @@ -529,19 +529,19 @@ class CustomWatchface : BaseWatchFace() { view.clearColorFilter() viewJson?.let { viewJson -> drawable?.let { - if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files + if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) not for svg files it.colorFilter = cwf.changeDrawableColor(dynData?.getFontColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) else it.clearColorFilter() view.setImageDrawable(it) } ?: apply { view.setImageDrawable(defaultDrawable?.let { cwf.resources.getDrawable(it) }) - if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) + if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // works on xml included into res files view.setColorFilter(dynData?.getFontColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) else view.clearColorFilter() } - if (view.drawable == null) + if (view.drawable == null) // if no drowable (either default, hardcoded or dynData, then apply color to background view.setBackgroundColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key, TRANSPARENT), Color.TRANSPARENT)) } } @@ -550,12 +550,12 @@ class CustomWatchface : BaseWatchFace() { customizeViewCommon(view) viewJson?.let { viewJson -> (dynData?.getDrawable() ?: textDrawable())?.let { - if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) or xml included into res, not for svg files - it.colorFilter = cwf.changeDrawableColor(dynData?.getFontColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) + if (viewJson.has(COLOR.key) || (dynData?.stepColor ?: 0) > 0) // Note only works on bitmap (png or jpg) not for svg files + it.colorFilter = cwf.changeDrawableColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key))) else it.clearColorFilter() view.background = it - } ?: apply { + } ?: apply { // if no drowable loaded, then apply color to background view.setBackgroundColor(dynData?.getColor() ?: cwf.getColor(viewJson.optString(COLOR.key, TRANSPARENT), Color.TRANSPARENT)) } } diff --git a/wear/src/main/res/values-nb-rNO/strings.xml b/wear/src/main/res/values-nb-rNO/strings.xml index 426e56cc75..a42336a2f1 100644 --- a/wear/src/main/res/values-nb-rNO/strings.xml +++ b/wear/src/main/res/values-nb-rNO/strings.xml @@ -36,7 +36,7 @@ Vis Gj.snittDelta Vis telefonbatteri Vis riggens batteri - Vis basalrate + Vis basaldose Vis loop status Vis BS Vis BGI @@ -71,7 +71,7 @@ Kalkulator i Meny Prime i menyen Enkel måleverdi - Kalkulator prosent + Kalkulatorprosent Handling ved trykk på komplikasjon Unicode i komplikasjoner Versjon: @@ -109,7 +109,7 @@ eKarbo Prosent Start [min] - Varighet [h] + Varighet [t] Insulin Forhåndsinnstilling 1 Forhåndsinnstilling 2