From 7fffbc7f3e9eb9ca80a8155064af69ef6a7b6f29 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Sat, 2 Mar 2024 10:16:21 +0100 Subject: [PATCH 01/22] Medtrum: Add additional checks to bolus packet --- .../comm/packets/NotificationPacket.kt | 106 ++++++++++++++---- .../comm/packets/NotificationPacketTest.kt | 96 ++++++++++++++++ 2 files changed, 183 insertions(+), 19 deletions(-) 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 929f5741658..0ea42b4f127 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 @@ -98,6 +98,25 @@ class NotificationPacket(val injector: HasAndroidInjector) { MASK_UNUSED_LEGACY to ::handleUnusedLegacy ) + 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 + ) + var newPatchStartTime = 0L init { @@ -131,6 +150,11 @@ class NotificationPacket(val injector: HasAndroidInjector) { return false } + if (!checkDataValidity(fieldMask, data)) { + aapsLogger.error(LTag.PUMPCOMM, "Invalid data in message") + return false + } + aapsLogger.debug(LTag.PUMPCOMM, "Message field mask: $fieldMask") for ((mask, handler) in maskHandlers) { @@ -145,25 +169,6 @@ class NotificationPacket(val injector: HasAndroidInjector) { private fun calculateExpectedLengthBasedOnFieldMask(fieldMask: Int): Int { var expectedLength = SIZE_FIELD_MASK - 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 @@ -173,6 +178,69 @@ class NotificationPacket(val injector: HasAndroidInjector) { return expectedLength } + private fun calculateOffset(fieldMask: Int, targetMask: Int): Int { + var offset = SIZE_FIELD_MASK // Start after the field mask itself + + for ((mask, size) in sizeMap) { + if (mask == targetMask) { + // Stop when we reach the target mask + return offset + } else if (fieldMask and mask != 0) { + // If the current mask is part of the field mask, add its size to the offset + offset += size + } + } + + // Code should never enter here, if does, it's a bug + throw IllegalArgumentException("Target mask not found in field mask") + } + + private fun checkDataValidity(fieldMask: Int, data: ByteArray): Boolean { + // Notification packet does not have crc check, so we check validity based on expected values in the packet + if (fieldMask and MASK_NORMAL_BOLUS != 0) { + val offset = calculateOffset(fieldMask, MASK_NORMAL_BOLUS) + val bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() * 0.05 + if (bolusDelivered < 0 || bolusDelivered > 50) { + aapsLogger.error(LTag.PUMPCOMM, "Invalid bolus delivered: $bolusDelivered") + return false + } + } + + if (fieldMask and MASK_BASAL != 0) { + val offset = calculateOffset(fieldMask, MASK_BASAL) + val basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() + val basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() + val basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 + if (medtrumPump.patchId != 0L && basalPatchId != medtrumPump.patchId) { + aapsLogger.error(LTag.PUMPCOMM, "Mismatched patch ID: $basalPatchId vs stored patchID: ${medtrumPump.patchId}") + return false + } + if (basalRate < 0 || basalRate > 40) { + aapsLogger.error(LTag.PUMPCOMM, "Invalid basal rate: $basalRate") + return false + } + } + + if (fieldMask and MASK_RESERVOIR != 0) { + val offset = calculateOffset(fieldMask, MASK_RESERVOIR) // You need to implement calculateOffset based on your mask handling + val reservoirValue = data.copyOfRange(offset, offset + SIZE_RESERVOIR).toInt() * 0.05 + if (reservoirValue < 0 || reservoirValue > 400) { + aapsLogger.error(LTag.PUMPCOMM, "Invalid reservoir value: $reservoirValue") + return false + } + } + + if (fieldMask and MASK_STORAGE != 0) { + val offset = calculateOffset(fieldMask, MASK_STORAGE) // Implement calculateOffset accordingly + val patchId = data.copyOfRange(offset + 2, offset + 4).toLong() // Assuming patch ID is at the end of the storage data + if (medtrumPump.patchId != 0L && patchId != medtrumPump.patchId) { + aapsLogger.error(LTag.PUMPCOMM, "Mismatched patch ID: $patchId vs stored patchID: ${medtrumPump.patchId}") + return false + } + } + return true + } + 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()) 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 106d0aa892f..1e87eb8f451 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 @@ -100,4 +100,100 @@ class NotificationPacketTest : MedtrumTestBase() { assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) assertThat(medtrumPump.currentSequenceNumber).isEqualTo(0) } + + // Testcase from real erroneous message + @Test fun handleNotificationGivenErroneousMessageThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 34, 17, -128, 4, -2, 20, -1, -89, 5, 6, 0, 18, 64, 35, 0, -54) + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.suspendTime).isEqualTo(0) + assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) + assertThat(medtrumPump.currentSequenceNumber).isEqualTo(0) + } + + @Test fun handleNotificationGivenBolusOutOfRangeThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 34, 17, -128, -128, -128, -89, 12, -80, 0, 14, 0, 0, 0, 0, 0, 0) + medtrumPump.bolusingTreatment = EventOverviewBolusProgress.Treatment(0.0, 0, false, 1) + // Set valid patchID (as in a started pump session) + medtrumPump.patchId = 14 + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.bolusDone).isTrue() + assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(0.0) + assertThat(medtrumPump.reservoir).isWithin(0.01).of(0.0) + } + + @Test fun handleNotificationGiveWrongPatchIDInBasalDataThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 40, 64, 6, 25, 0, 14, 0, 84, -93, -83, 17, 17, 64, 0, -104, 15, 0, 16) + // Set a valid patchID (as in a started pump session) + medtrumPump.patchId = 15 + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.lastBasalType).isEqualTo(BasalType.NONE) + assertThat(medtrumPump.lastBasalRate).isWithin(0.01).of(0.0) + assertThat(medtrumPump.lastBasalSequence).isEqualTo(0) + assertThat(medtrumPump.lastBasalPatchId).isEqualTo(0) + assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) + assertThat(medtrumPump.reservoir).isWithin(0.01).of(0.0) + } + + @Test fun handleNotificationGiveBasalOutOfRangeInBasalDataThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 40, 64, 6, 25, 0, 14, 0, 84, -93, -83, 17, 127, 127, -128, -104, 14, 0, 16) + // Set a valid patchID (as in a started pump session) + medtrumPump.patchId = 14 + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.lastBasalType).isEqualTo(BasalType.NONE) + assertThat(medtrumPump.lastBasalRate).isWithin(0.01).of(0.0) + assertThat(medtrumPump.lastBasalSequence).isEqualTo(0) + assertThat(medtrumPump.lastBasalPatchId).isEqualTo(0) + assertThat(medtrumPump.lastBasalStartTime).isEqualTo(0) + assertThat(medtrumPump.reservoir).isWithin(0.01).of(0.0) + } + + @Test fun handleNotificationGivenReservoirOutOfRangeThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 34, 16, 0, 3, 0, -128, -128, 0, 0, 0, 0, 0) + medtrumPump.bolusingTreatment = EventOverviewBolusProgress.Treatment(0.0, 0, false, 1) + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.bolusDone).isTrue() + assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(0.0) + assertThat(medtrumPump.reservoir).isWithin(0.01).of(0.0) + } + + @Test fun handleNotificationGivenWrongPatchIDThenNothingSaved() { + // Inputs + val data = byteArrayOf(32, 34, 17, -128, 33, 0, -89, 12, -80, 0, 15, 0, 0, 0, 0, 0, 0) + medtrumPump.bolusingTreatment = EventOverviewBolusProgress.Treatment(0.0, 0, false, 1) + // Set valid patchID (as in a started pump session) + medtrumPump.patchId = 14 + + // Call + NotificationPacket(packetInjector).handleNotification(data) + + // Expected values + assertThat(medtrumPump.bolusDone).isTrue() + assertThat(medtrumPump.bolusingTreatment!!.insulin).isWithin(0.01).of(0.0) + assertThat(medtrumPump.reservoir).isWithin(0.01).of(0.0) + } } From 2afcbd80916d6ef949d4420d20532725a0b21e76 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Sat, 2 Mar 2024 10:20:17 +0100 Subject: [PATCH 02/22] 3.2.0.4-dev --- buildSrc/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 340ef9ae8aa..cde8d4abcef 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -2,7 +2,7 @@ import org.gradle.api.JavaVersion object Versions { - const val appVersion = "3.2.0.4" + const val appVersion = "3.2.0.4-dev" const val versionCode = 1500 const val ndkVersion = "21.1.6352462" From 2bbf05ad5505d985c1bc15bbf7396f0519a8aaa3 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 3 Mar 2024 10:15:39 +0100 Subject: [PATCH 03/22] fix max bolus in pregnant mode --- plugins/constraints/src/main/res/xml/pref_safety.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/constraints/src/main/res/xml/pref_safety.xml b/plugins/constraints/src/main/res/xml/pref_safety.xml index 180e13c3b55..04ac7dd49f2 100644 --- a/plugins/constraints/src/main/res/xml/pref_safety.xml +++ b/plugins/constraints/src/main/res/xml/pref_safety.xml @@ -21,7 +21,7 @@ android:inputType="numberDecimal" android:key="@string/key_treatmentssafety_maxbolus" android:title="@string/max_bolus_title" - validate:floatmaxNumber="25.0" + validate:floatmaxNumber="60.0" validate:floatminNumber="0.1" validate:testType="floatNumericRange" /> From 4340b0bc57d20870559abfeb254254419dac1dec Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 11 Mar 2024 21:13:31 +0100 Subject: [PATCH 04/22] Equil support --- app/build.gradle.kts | 1 + .../main/kotlin/app/aaps/di/AppComponent.kt | 2 + .../kotlin/app/aaps/di/PluginsListModule.kt | 7 + .../interfaces/pump/defs/ManufacturerType.kt | 3 +- .../core/interfaces/pump/defs/PumpType.kt | 21 +- .../aaps/core/main/pump/PumpTypeExtension.kt | 3 + .../ui/src/main/res/drawable/ic_equil_128.png | Bin 0 -> 15824 bytes crowdin.yml | 2 + .../app/aaps/database/entities/UserEntry.kt | 1 + .../entities/embedments/InterfaceIDs.kt | 1 + .../UserEntryPresentationHelperImpl.kt | 1 + pump/equil/.gitignore | 1 + pump/equil/build.gradle.kts | 44 + pump/equil/proguard-rules.pro | 21 + .../11.json | 234 ++++ pump/equil/src/main/AndroidManifest.xml | 38 + .../java/app/aaps/pump/equil/EquilConst.kt | 25 + .../java/app/aaps/pump/equil/EquilFragment.kt | 394 ++++++ .../app/aaps/pump/equil/EquilPumpPlugin.kt | 478 +++++++ .../java/app/aaps/pump/equil/ble/EquilBLE.kt | 438 +++++++ .../app/aaps/pump/equil/ble/GattAttributes.kt | 11 + .../app/aaps/pump/equil/data/AlarmMode.kt | 12 + .../app/aaps/pump/equil/data/BolusProfile.kt | 8 + .../java/app/aaps/pump/equil/data/RunMode.kt | 8 + .../aaps/pump/equil/database/Converters.kt | 27 + .../equil/database/EquilHistoryDatabase.kt | 31 + .../pump/equil/database/EquilHistoryPump.kt | 29 + .../equil/database/EquilHistoryPumpDao.kt | 32 + .../pump/equil/database/EquilHistoryRecord.kt | 64 + .../equil/database/EquilHistoryRecordDao.kt | 27 + .../aaps/pump/equil/database/EquilRecord.kt | 13 + .../pump/equil/di/EquilActivitiesModule.kt | 48 + .../aaps/pump/equil/di/EquilHistoryModule.kt | 28 + .../app/aaps/pump/equil/di/EquilModule.kt | 13 + .../aaps/pump/equil/di/EquilServicesModule.kt | 12 + .../driver/definition/ActivationProgress.kt | 21 + .../equil/driver/definition/BasalSchedule.kt | 52 + .../driver/definition/BasalScheduleEntry.kt | 5 + .../definition/BluetoothConnectionState.kt | 5 + .../definition/EquilHistoryEntryGroup.kt | 37 + .../equil/events/EventEquilDataChanged.kt | 5 + .../equil/events/EventEquilInsulinChanged.kt | 5 + .../equil/events/EventEquilModeChanged.kt | 5 + .../equil/events/EventEquilUnPairChanged.kt | 5 + .../app/aaps/pump/equil/manager/AESUtil.java | 87 ++ .../java/app/aaps/pump/equil/manager/Crc.java | 49 + .../pump/equil/manager/EquilCmdModel.java | 51 + .../aaps/pump/equil/manager/EquilManager.java | 1157 +++++++++++++++++ .../pump/equil/manager/EquilResponse.java | 60 + .../app/aaps/pump/equil/manager/Utils.java | 162 +++ .../pump/equil/manager/command/BaseCmd.java | 312 +++++ .../equil/manager/command/BaseSetting.java | 144 ++ .../equil/manager/command/CmdAlarmSet.java | 64 + .../equil/manager/command/CmdBasalGet.java | 64 + .../equil/manager/command/CmdBasalSet.java | 88 ++ .../equil/manager/command/CmdDevicesGet.java | 49 + .../manager/command/CmdDevicesOldGet.java | 186 +++ .../manager/command/CmdExtendedBolusSet.java | 87 ++ .../equil/manager/command/CmdHistoryGet.java | 125 ++ .../manager/command/CmdInsulinChange.java | 48 + .../equil/manager/command/CmdInsulinGet.java | 47 + .../manager/command/CmdLargeBasalSet.java | 73 ++ .../equil/manager/command/CmdModelGet.java | 47 + .../equil/manager/command/CmdModelSet.java | 66 + .../pump/equil/manager/command/CmdPair.java | 165 +++ .../manager/command/CmdResistanceGet.java | 61 + .../equil/manager/command/CmdSettingGet.java | 137 ++ .../equil/manager/command/CmdSettingSet.java | 82 ++ .../equil/manager/command/CmdStatusGet.java | 13 + .../equil/manager/command/CmdStepSet.java | 62 + .../manager/command/CmdTempBasalGet.java | 66 + .../manager/command/CmdTempBasalSet.java | 89 ++ .../equil/manager/command/CmdTimeGet.java | 46 + .../equil/manager/command/CmdTimeSet.java | 58 + .../manager/command/CmdTmepBasalGet.java | 38 + .../pump/equil/manager/command/CmdUnPair.java | 145 +++ .../pump/equil/manager/command/PumpEvent.java | 105 ++ .../equil/manager/command/SettingCmd.java | 54 + .../equil/ui/EquilHistoryRecordActivity.kt | 482 +++++++ .../aaps/pump/equil/ui/EquilUnPairActivity.kt | 122 ++ .../equil/ui/EquilUnPairDetachActivity.kt | 124 ++ .../pump/equil/ui/dlg/EquilAutoDressingDlg.kt | 78 ++ .../equil/ui/dlg/EquilChangeInsulinDlg.kt | 74 ++ .../pump/equil/ui/dlg/EquilPairConfigDlg.kt | 71 + .../aaps/pump/equil/ui/dlg/EquilUnPairDlg.kt | 69 + .../app/aaps/pump/equil/ui/dlg/LoadingDlg.kt | 66 + .../ui/pair/EquilChangeInsulinFragment.kt | 60 + .../pump/equil/ui/pair/EquilPairActivity.kt | 108 ++ .../equil/ui/pair/EquilPairAirFragment.kt | 173 +++ .../ui/pair/EquilPairAssembleFragment.kt | 42 + .../equil/ui/pair/EquilPairAttachFragment.kt | 39 + .../equil/ui/pair/EquilPairConfirmFragment.kt | 119 ++ .../equil/ui/pair/EquilPairFillFragment.kt | 167 +++ .../equil/ui/pair/EquilPairFragmentBase.kt | 126 ++ .../ui/pair/EquilPairSerialNumberFragment.kt | 372 ++++++ .../main/res/drawable-xhdpi/equil_ic_pump.png | Bin 0 -> 715267 bytes .../res/drawable-xhdpi/equil_ic_pump2.png | Bin 0 -> 3664641 bytes .../drawable-xhdpi/equil_ic_trend_right.png | Bin 0 -> 2336 bytes .../drawable/baseline_arrow_drop_down_24.xml | 10 + .../equil_animation_wizard_assemble.gif | Bin 0 -> 111940 bytes .../equil_animation_wizard_attach.gif | Bin 0 -> 265478 bytes .../equil_animation_wizard_detach.gif | Bin 0 -> 269687 bytes .../equil_ic_bluetooth_connecting.xml | 16 + .../main/res/drawable/equil_ic_dressing.xml | 12 + .../main/res/drawable/equil_ic_hositry.xml | 13 + .../main/res/drawable/equil_ic_settings.xml | 5 + .../main/res/drawable/equil_line_point.xml | 5 + .../res/drawable/equil_selector_radiobtn.xml | 15 + .../ic_equil_overview_resume_delivery.xml | 17 + .../ic_equil_overview_suspend_delivery.xml | 17 + .../res/drawable/ic_equil_resume_delivery.xml | 9 + .../src/main/res/drawable/ic_pod_128.xml | 36 + .../res/layout/equil_auto_dressing_dialog.xml | 50 + .../equil_common_wizard_nav_buttons.xml | 25 + ...quil_common_wizard_progress_indication.xml | 13 + .../layout/equil_dialog_alert_dressing.xml | 80 ++ .../main/res/layout/equil_dialog_binds.xml | 43 + pump/equil/src/main/res/layout/equil_fra.xml | 420 ++++++ .../res/layout/equil_history_activity.xml | 58 + .../layout/equil_history_record_activity.xml | 72 + .../main/res/layout/equil_item_chooice.xml | 15 + .../src/main/res/layout/equil_item_record.xml | 30 + .../main/res/layout/equil_line_horizontal.xml | 8 + .../main/res/layout/equil_line_vertical.xml | 4 + .../main/res/layout/equil_pair_activity.xml | 12 + .../res/layout/equil_pair_air_fragment.xml | 50 + .../layout/equil_pair_assemble_fragment.xml | 40 + .../res/layout/equil_pair_attach_fragment.xml | 39 + .../res/layout/equil_pair_base_fragment.xml | 32 + .../equil_pair_change_insulin_fragment.xml | 43 + .../res/layout/equil_pair_config_dialog.xml | 73 ++ .../layout/equil_pair_confirm_fragment.xml | 41 + .../layout/equil_pair_devices_activity.xml | 79 ++ .../res/layout/equil_pair_fill_fragment.xml | 64 + .../layout/equil_pair_install_activity.xml | 71 + .../main/res/layout/equil_pair_progress.xml | 13 + .../equil_pair_serial_number_fragment.xml | 109 ++ .../main/res/layout/equil_unpair_activity.xml | 71 + .../layout/equil_unpair_detach_activity.xml | 71 + .../main/res/layout/equil_unpair_dialog.xml | 80 ++ .../src/main/res/layout/loading_dialog.xml | 14 + .../equil_pair_navigation_graph.xml | 63 + .../src/main/res/values-ar-rSA/strings.xml | 6 + .../src/main/res/values-bg-rBG/strings.xml | 6 + .../src/main/res/values-ca-rES/strings.xml | 6 + .../src/main/res/values-cs-rCZ/strings.xml | 181 +++ .../src/main/res/values-da-rDK/strings.xml | 6 + .../src/main/res/values-de-rDE/strings.xml | 6 + .../src/main/res/values-el-rGR/strings.xml | 6 + .../src/main/res/values-es-rES/strings.xml | 174 +++ .../src/main/res/values-fr-rFR/strings.xml | 174 +++ .../src/main/res/values-hr-rHR/strings.xml | 6 + .../src/main/res/values-hu-rHU/strings.xml | 6 + .../src/main/res/values-it-rIT/strings.xml | 175 +++ .../src/main/res/values-iw-rIL/strings.xml | 9 + .../src/main/res/values-ko-rKR/strings.xml | 6 + .../src/main/res/values-lt-rLT/strings.xml | 6 + .../src/main/res/values-nb-rNO/strings.xml | 174 +++ .../src/main/res/values-nl-rNL/strings.xml | 65 + .../src/main/res/values-pl-rPL/strings.xml | 6 + .../src/main/res/values-pt-rBR/strings.xml | 6 + .../src/main/res/values-pt-rPT/strings.xml | 6 + .../src/main/res/values-ro-rRO/strings.xml | 6 + .../src/main/res/values-ru-rRU/strings.xml | 181 +++ .../src/main/res/values-sk-rSK/strings.xml | 181 +++ .../src/main/res/values-sr-rCS/strings.xml | 6 + .../src/main/res/values-sv-rSE/strings.xml | 6 + .../src/main/res/values-tr-rTR/strings.xml | 174 +++ .../src/main/res/values-uk-rUA/strings.xml | 6 + .../src/main/res/values-zh-rCN/strings.xml | 6 + pump/equil/src/main/res/values/arrays.xml | 18 + pump/equil/src/main/res/values/colors.xml | 8 + pump/equil/src/main/res/values/strings.xml | 216 +++ pump/equil/src/main/res/values/styles.xml | 21 + pump/equil/src/main/res/xml/pref_equil.xml | 39 + settings.gradle | 1 + 176 files changed, 12432 insertions(+), 2 deletions(-) create mode 100644 core/ui/src/main/res/drawable/ic_equil_128.png create mode 100644 pump/equil/.gitignore create mode 100644 pump/equil/build.gradle.kts create mode 100644 pump/equil/proguard-rules.pro create mode 100644 pump/equil/schemas/app.aaps.pump.equil.database.EquilHistoryDatabase/11.json create mode 100644 pump/equil/src/main/AndroidManifest.xml create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/EquilConst.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/EquilFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/EquilPumpPlugin.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ble/EquilBLE.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ble/GattAttributes.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/data/AlarmMode.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/data/BolusProfile.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/data/RunMode.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/Converters.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryDatabase.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPump.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPumpDao.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecord.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecordDao.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/database/EquilRecord.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/di/EquilActivitiesModule.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/di/EquilHistoryModule.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/di/EquilModule.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/di/EquilServicesModule.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/ActivationProgress.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalSchedule.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalScheduleEntry.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BluetoothConnectionState.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/EquilHistoryEntryGroup.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilDataChanged.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilInsulinChanged.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilModeChanged.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilUnPairChanged.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/AESUtil.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/Crc.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilCmdModel.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilManager.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilResponse.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/Utils.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseCmd.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseSetting.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdAlarmSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesOldGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdExtendedBolusSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdHistoryGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinChange.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdLargeBasalSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdPair.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdResistanceGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStatusGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStepSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeSet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTmepBasalGet.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdUnPair.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/PumpEvent.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/manager/command/SettingCmd.java create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilHistoryRecordActivity.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairActivity.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairDetachActivity.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilAutoDressingDlg.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilChangeInsulinDlg.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilPairConfigDlg.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilUnPairDlg.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/LoadingDlg.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilChangeInsulinFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairActivity.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairAirFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairAssembleFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairAttachFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairConfirmFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairFillFragment.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairFragmentBase.kt create mode 100644 pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilPairSerialNumberFragment.kt create mode 100644 pump/equil/src/main/res/drawable-xhdpi/equil_ic_pump.png create mode 100644 pump/equil/src/main/res/drawable-xhdpi/equil_ic_pump2.png create mode 100644 pump/equil/src/main/res/drawable-xhdpi/equil_ic_trend_right.png create mode 100644 pump/equil/src/main/res/drawable/baseline_arrow_drop_down_24.xml create mode 100644 pump/equil/src/main/res/drawable/equil_animation_wizard_assemble.gif create mode 100644 pump/equil/src/main/res/drawable/equil_animation_wizard_attach.gif create mode 100644 pump/equil/src/main/res/drawable/equil_animation_wizard_detach.gif create mode 100644 pump/equil/src/main/res/drawable/equil_ic_bluetooth_connecting.xml create mode 100644 pump/equil/src/main/res/drawable/equil_ic_dressing.xml create mode 100644 pump/equil/src/main/res/drawable/equil_ic_hositry.xml create mode 100644 pump/equil/src/main/res/drawable/equil_ic_settings.xml create mode 100644 pump/equil/src/main/res/drawable/equil_line_point.xml create mode 100644 pump/equil/src/main/res/drawable/equil_selector_radiobtn.xml create mode 100644 pump/equil/src/main/res/drawable/ic_equil_overview_resume_delivery.xml create mode 100644 pump/equil/src/main/res/drawable/ic_equil_overview_suspend_delivery.xml create mode 100644 pump/equil/src/main/res/drawable/ic_equil_resume_delivery.xml create mode 100644 pump/equil/src/main/res/drawable/ic_pod_128.xml create mode 100644 pump/equil/src/main/res/layout/equil_auto_dressing_dialog.xml create mode 100644 pump/equil/src/main/res/layout/equil_common_wizard_nav_buttons.xml create mode 100644 pump/equil/src/main/res/layout/equil_common_wizard_progress_indication.xml create mode 100644 pump/equil/src/main/res/layout/equil_dialog_alert_dressing.xml create mode 100644 pump/equil/src/main/res/layout/equil_dialog_binds.xml create mode 100644 pump/equil/src/main/res/layout/equil_fra.xml create mode 100644 pump/equil/src/main/res/layout/equil_history_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_history_record_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_item_chooice.xml create mode 100644 pump/equil/src/main/res/layout/equil_item_record.xml create mode 100644 pump/equil/src/main/res/layout/equil_line_horizontal.xml create mode 100644 pump/equil/src/main/res/layout/equil_line_vertical.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_air_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_assemble_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_attach_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_base_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_change_insulin_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_config_dialog.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_confirm_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_devices_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_fill_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_install_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_progress.xml create mode 100644 pump/equil/src/main/res/layout/equil_pair_serial_number_fragment.xml create mode 100644 pump/equil/src/main/res/layout/equil_unpair_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_unpair_detach_activity.xml create mode 100644 pump/equil/src/main/res/layout/equil_unpair_dialog.xml create mode 100644 pump/equil/src/main/res/layout/loading_dialog.xml create mode 100644 pump/equil/src/main/res/navigation/equil_pair_navigation_graph.xml create mode 100644 pump/equil/src/main/res/values-ar-rSA/strings.xml create mode 100644 pump/equil/src/main/res/values-bg-rBG/strings.xml create mode 100644 pump/equil/src/main/res/values-ca-rES/strings.xml create mode 100644 pump/equil/src/main/res/values-cs-rCZ/strings.xml create mode 100644 pump/equil/src/main/res/values-da-rDK/strings.xml create mode 100644 pump/equil/src/main/res/values-de-rDE/strings.xml create mode 100644 pump/equil/src/main/res/values-el-rGR/strings.xml create mode 100644 pump/equil/src/main/res/values-es-rES/strings.xml create mode 100644 pump/equil/src/main/res/values-fr-rFR/strings.xml create mode 100644 pump/equil/src/main/res/values-hr-rHR/strings.xml create mode 100644 pump/equil/src/main/res/values-hu-rHU/strings.xml create mode 100644 pump/equil/src/main/res/values-it-rIT/strings.xml create mode 100644 pump/equil/src/main/res/values-iw-rIL/strings.xml create mode 100644 pump/equil/src/main/res/values-ko-rKR/strings.xml create mode 100644 pump/equil/src/main/res/values-lt-rLT/strings.xml create mode 100644 pump/equil/src/main/res/values-nb-rNO/strings.xml create mode 100644 pump/equil/src/main/res/values-nl-rNL/strings.xml create mode 100644 pump/equil/src/main/res/values-pl-rPL/strings.xml create mode 100644 pump/equil/src/main/res/values-pt-rBR/strings.xml create mode 100644 pump/equil/src/main/res/values-pt-rPT/strings.xml create mode 100644 pump/equil/src/main/res/values-ro-rRO/strings.xml create mode 100644 pump/equil/src/main/res/values-ru-rRU/strings.xml create mode 100644 pump/equil/src/main/res/values-sk-rSK/strings.xml create mode 100644 pump/equil/src/main/res/values-sr-rCS/strings.xml create mode 100644 pump/equil/src/main/res/values-sv-rSE/strings.xml create mode 100644 pump/equil/src/main/res/values-tr-rTR/strings.xml create mode 100644 pump/equil/src/main/res/values-uk-rUA/strings.xml create mode 100644 pump/equil/src/main/res/values-zh-rCN/strings.xml create mode 100644 pump/equil/src/main/res/values/arrays.xml create mode 100644 pump/equil/src/main/res/values/colors.xml create mode 100644 pump/equil/src/main/res/values/strings.xml create mode 100644 pump/equil/src/main/res/values/styles.xml create mode 100644 pump/equil/src/main/res/xml/pref_equil.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8db9f1ace1c..c3c3d2b4655 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -198,6 +198,7 @@ dependencies { implementation(project(":pump:danar")) implementation(project(":pump:diaconn")) implementation(project(":pump:eopatch")) + implementation(project(":pump:equil")) implementation(project(":pump:medtrum")) implementation(project(":insight")) implementation(project(":pump:medtronic")) diff --git a/app/src/main/kotlin/app/aaps/di/AppComponent.kt b/app/src/main/kotlin/app/aaps/di/AppComponent.kt index c395f47632a..646f6d89e4f 100644 --- a/app/src/main/kotlin/app/aaps/di/AppComponent.kt +++ b/app/src/main/kotlin/app/aaps/di/AppComponent.kt @@ -14,6 +14,7 @@ import app.aaps.plugins.main.di.PluginsModule import app.aaps.plugins.source.di.SourceModule import app.aaps.plugins.sync.di.OpenHumansModule import app.aaps.plugins.sync.di.SyncModule +import app.aaps.pump.equil.di.EquilModule import app.aaps.pump.virtual.di.VirtualPumpModule import app.aaps.shared.impl.di.SharedImplModule import app.aaps.ui.di.UiModule @@ -76,6 +77,7 @@ import javax.inject.Singleton DanaRSModule::class, DiaconnG8Module::class, EopatchModule::class, + EquilModule::class, InsightModule::class, InsightDatabaseModule::class, MedtronicModule::class, diff --git a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt index 36c1cd22c0a..071ded42159 100644 --- a/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt +++ b/app/src/main/kotlin/app/aaps/di/PluginsListModule.kt @@ -53,6 +53,7 @@ import app.aaps.plugins.sync.nsclientV3.NSClientV3Plugin import app.aaps.plugins.sync.openhumans.OpenHumansUploaderPlugin import app.aaps.plugins.sync.tidepool.TidepoolPlugin import app.aaps.plugins.sync.xdrip.XdripPlugin +import app.aaps.pump.equil.EquilPumpPlugin import app.aaps.pump.virtual.VirtualPumpPlugin import dagger.Binds import dagger.Module @@ -224,6 +225,12 @@ abstract class PluginsListModule { @AllConfigs @IntoMap @IntKey(170) + abstract fun bindEquilPumpPlugin(plugin: EquilPumpPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(180) abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase @Binds diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/ManufacturerType.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/ManufacturerType.kt index 71125404a22..5197b92b02d 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/ManufacturerType.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/ManufacturerType.kt @@ -12,5 +12,6 @@ enum class ManufacturerType(val description: String) { Roche("Roche"), Ypsomed("Ypsomed"), G2e("G2e"), - Eoflow("Eoflow"); + Eoflow("Eoflow"), + Equil("Equil"); } \ No newline at end of file diff --git a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/PumpType.kt b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/PumpType.kt index ebf385556e1..59bbd60ddfd 100644 --- a/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/PumpType.kt +++ b/core/interfaces/src/main/kotlin/app/aaps/core/interfaces/pump/defs/PumpType.kt @@ -434,6 +434,24 @@ enum class PumpType { description = "Medtrum untested", model = "untested", parent = MEDTRUM_NANO + ), + EQUIL( + description = "Equil", + manufacturer = ManufacturerType.Equil, + model = "Equil", + bolusSize = 0.05, + specialBolusSize = null, + extendedBolusSettings = DoseSettings(0.05, 30, 5 * 60, 0.05), + pumpTempBasalType = PumpTempBasalType.Absolute, + tbrSettings = DoseSettings(0.05, 30, 24 * 60, 0.0, 35.0), + specialBasalDurations = PumpCapability.BasalRate_Duration30minAllowed, + baseBasalMinValue = 0.05, + baseBasalMaxValue = 3.0, + baseBasalStep = 0.01, + baseBasalSpecialSteps = null, + pumpCapability = PumpCapability.DiaconnCapabilities, + source = Source.EQuil, + useHardwareLink = true, ); val description: String @@ -506,7 +524,8 @@ enum class PumpType { Medtrum, MDI, VirtualPump, - Unknown + Unknown, + EQuil } companion object { diff --git a/core/main/src/main/kotlin/app/aaps/core/main/pump/PumpTypeExtension.kt b/core/main/src/main/kotlin/app/aaps/core/main/pump/PumpTypeExtension.kt index 17676bbfcca..16f95355dbd 100644 --- a/core/main/src/main/kotlin/app/aaps/core/main/pump/PumpTypeExtension.kt +++ b/core/main/src/main/kotlin/app/aaps/core/main/pump/PumpTypeExtension.kt @@ -59,6 +59,7 @@ fun PumpType.Companion.fromDbPumpType(pt: InterfaceIDs.PumpType): PumpType = InterfaceIDs.PumpType.USER -> PumpType.USER InterfaceIDs.PumpType.DIACONN_G8 -> PumpType.DIACONN_G8 InterfaceIDs.PumpType.EOPATCH2 -> PumpType.EOFLOW_EOPATCH2 + InterfaceIDs.PumpType.EQUIL -> PumpType.EQUIL InterfaceIDs.PumpType.MEDTRUM -> PumpType.MEDTRUM_NANO InterfaceIDs.PumpType.MEDTRUM_300U -> PumpType.MEDTRUM_300U InterfaceIDs.PumpType.MEDTRUM_UNTESTED -> PumpType.MEDTRUM_UNTESTED @@ -84,6 +85,7 @@ fun PumpType.Source.toDbSource(): UserEntry.Sources = PumpType.Source.Medtrum -> UserEntry.Sources.Medtrum PumpType.Source.MDI -> UserEntry.Sources.MDI PumpType.Source.VirtualPump -> UserEntry.Sources.VirtualPump + PumpType.Source.EQuil -> UserEntry.Sources.Equil else -> UserEntry.Sources.Unknown } @@ -121,6 +123,7 @@ fun PumpType.toDbPumpType(): InterfaceIDs.PumpType = PumpType.USER -> InterfaceIDs.PumpType.USER PumpType.DIACONN_G8 -> InterfaceIDs.PumpType.DIACONN_G8 PumpType.EOFLOW_EOPATCH2 -> InterfaceIDs.PumpType.EOPATCH2 + PumpType.EQUIL -> InterfaceIDs.PumpType.EQUIL PumpType.MEDTRUM_NANO -> InterfaceIDs.PumpType.MEDTRUM PumpType.MEDTRUM_300U -> InterfaceIDs.PumpType.MEDTRUM_300U PumpType.MEDTRUM_UNTESTED -> InterfaceIDs.PumpType.MEDTRUM_UNTESTED diff --git a/core/ui/src/main/res/drawable/ic_equil_128.png b/core/ui/src/main/res/drawable/ic_equil_128.png new file mode 100644 index 0000000000000000000000000000000000000000..3944569d7b953b3f9511776cca22ee898f36921a GIT binary patch literal 15824 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 nTwgz4+c1M|{-4cb5MZbL4 R.drawable.ic_patch_pump_outline Sources.OmnipodDash -> R.drawable.ic_patch_pump_outline Sources.EOPatch2 -> app.aaps.core.ui.R.drawable.ic_eopatch2_128 + Sources.Equil -> app.aaps.core.ui.R.drawable.ic_equil_128 Sources.Medtrum -> app.aaps.core.ui.R.drawable.ic_medtrum_128 Sources.MDI -> R.drawable.ic_ict Sources.VirtualPump -> R.drawable.ic_virtual_pump diff --git a/pump/equil/.gitignore b/pump/equil/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/pump/equil/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/pump/equil/build.gradle.kts b/pump/equil/build.gradle.kts new file mode 100644 index 00000000000..3fbfcab163a --- /dev/null +++ b/pump/equil/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.library") + id("kotlin-android") + id("kotlin-kapt") + id("android-module-dependencies") + id("test-module-dependencies") + id("jacoco-module-dependencies") +} + + +android { + + namespace = "app.aaps.pump.equil" + defaultConfig { + kapt { + arguments { + arg("room.incremental", "true") + arg("room.schemaLocation", "$projectDir/schemas") + } + } + } +} + +dependencies { + implementation(project(":core:interfaces")) + implementation(project(":core:libraries")) + implementation(project(":core:main")) + implementation(project(":core:utils")) + implementation(project(":core:ui")) + implementation(project(":core:validators")) + implementation(project(":pump:pump-common")) + + api(Libs.AndroidX.fragment) + api(Libs.AndroidX.navigationFragment) + + api(Libs.AndroidX.Room.room) + api(Libs.AndroidX.Room.runtime) + api(Libs.AndroidX.Room.rxJava3) + kapt(Libs.AndroidX.Room.compiler) + kapt(Libs.Dagger.compiler) + kapt(Libs.Dagger.androidProcessor) + + implementation("com.github.bumptech.glide:glide:4.16.0") +} diff --git a/pump/equil/proguard-rules.pro b/pump/equil/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/pump/equil/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/pump/equil/schemas/app.aaps.pump.equil.database.EquilHistoryDatabase/11.json b/pump/equil/schemas/app.aaps.pump.equil.database.EquilHistoryDatabase/11.json new file mode 100644 index 00000000000..b314448bcb4 --- /dev/null +++ b/pump/equil/schemas/app.aaps.pump.equil.database.EquilHistoryDatabase/11.json @@ -0,0 +1,234 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "f31b53d9fb1aa34b993fc11767a645d5", + "entities": [ + { + "tableName": "equilHistoryPump", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `code` INTEGER NOT NULL, `battery` INTEGER NOT NULL, `insulin` INTEGER NOT NULL, `rate` INTEGER NOT NULL, `largeRate` INTEGER NOT NULL, `type` INTEGER NOT NULL, `eventIndex` INTEGER NOT NULL, `level` INTEGER NOT NULL, `parm` INTEGER NOT NULL, `port` INTEGER NOT NULL, `eventTimestamp` INTEGER NOT NULL, `serialNumber` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "battery", + "columnName": "battery", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insulin", + "columnName": "insulin", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rate", + "columnName": "rate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "largeRate", + "columnName": "largeRate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventIndex", + "columnName": "eventIndex", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "parm", + "columnName": "parm", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventTimestamp", + "columnName": "eventTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serialNumber", + "columnName": "serialNumber", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_equilHistoryPump_code_timestamp_eventTimestamp_eventIndex", + "unique": false, + "columnNames": [ + "code", + "timestamp", + "eventTimestamp", + "eventIndex" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_equilHistoryPump_code_timestamp_eventTimestamp_eventIndex` ON `${TABLE_NAME}` (`code`, `timestamp`, `eventTimestamp`, `eventIndex`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "equilHistoryRecord", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` TEXT, `timestamp` INTEGER NOT NULL, `serialNumber` TEXT NOT NULL, `resolvedStatus` TEXT, `resolvedAt` INTEGER, `note` TEXT, `tempBasalRecord_duration` INTEGER, `tempBasalRecord_rate` REAL, `tempBasalRecord_startTime` INTEGER, `bolusRecord_amount` REAL, `bolusRecord_bolusType` TEXT, `bolusRecord_startTime` INTEGER, `basalprofile_segments` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "serialNumber", + "columnName": "serialNumber", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resolvedStatus", + "columnName": "resolvedStatus", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "resolvedAt", + "columnName": "resolvedAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tempBasalRecord.duration", + "columnName": "tempBasalRecord_duration", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "tempBasalRecord.rate", + "columnName": "tempBasalRecord_rate", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "tempBasalRecord.startTime", + "columnName": "tempBasalRecord_startTime", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bolusRecord.amount", + "columnName": "bolusRecord_amount", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "bolusRecord.bolusType", + "columnName": "bolusRecord_bolusType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bolusRecord.startTime", + "columnName": "bolusRecord_startTime", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "basalValuesRecord.segments", + "columnName": "basalprofile_segments", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_equilHistoryRecord_type_timestamp", + "unique": false, + "columnNames": [ + "type", + "timestamp" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_equilHistoryRecord_type_timestamp` ON `${TABLE_NAME}` (`type`, `timestamp`)" + } + ], + "foreignKeys": [] + } + ], + "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, 'f31b53d9fb1aa34b993fc11767a645d5')" + ] + } +} \ No newline at end of file diff --git a/pump/equil/src/main/AndroidManifest.xml b/pump/equil/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..0eaea1cfe44 --- /dev/null +++ b/pump/equil/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/EquilConst.kt b/pump/equil/src/main/java/app/aaps/pump/equil/EquilConst.kt new file mode 100644 index 00000000000..eefdec15ac2 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/EquilConst.kt @@ -0,0 +1,25 @@ +package app.aaps.pump.equil + +object EquilConst { + + const val EQUIL_CMD_TIME_OUT: Long = 300000 + const val EQUIL_BLE_WRITE_TIME_OUT: Long = 20 + const val EQUIL_BLE_NEXT_CMD: Long = 150 + const val EQUIL_SUPPORT_LEVEL = 5.3f + const val EQUIL_BLOUS_THRESHOLD_STEP = 1600 + const val EQUIL_STEP_MAX = 32000 + const val EQUIL_STEP_FILL = 160 + const val EQUIL_STEP_AIR = 120 + object Prefs { + + val EQUIL_DEVICES = R.string.key_equil_devices + val EQUIL_PASSWORD = R.string.key_equil_password + val Equil_ALARM_BATTERY_10 = R.string.key_equil_alarm_battery_10 + val EQUIL_ALARM_INSULIN_10 = R.string.key_equil_alarm_insulin_10 + val EQUIL_ALARM_INSULIN_5 = R.string.key_equil_alarm_insulin_5 + val EQUIL_BASAL_SET = R.string.key_equil_basal_set + val EQUIL_STATE = R.string.key_equil_state + val EQUIL_ALARM_BATTERY = R.string.key_equil_alarm_battery + val EQUIL_ALARM_INSULIN = R.string.key_equil_alarm_insulin + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/EquilFragment.kt b/pump/equil/src/main/java/app/aaps/pump/equil/EquilFragment.kt new file mode 100644 index 00000000000..371f861431f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/EquilFragment.kt @@ -0,0 +1,394 @@ +package app.aaps.pump.equil + +import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.os.Handler +import android.os.HandlerThread +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import app.aaps.core.interfaces.extensions.runOnUiThread +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.logging.UserEntryLogger +import app.aaps.core.interfaces.plugin.ActivePlugin +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.pump.WarnColors +import app.aaps.core.interfaces.queue.Callback +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.AapsSchedulers +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.EventPumpStatusChanged +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.utils.DateUtil +import app.aaps.core.interfaces.utils.T +import app.aaps.core.interfaces.utils.fabric.FabricPrivacy +import app.aaps.core.ui.UIRunnable +import app.aaps.core.ui.toast.ToastUtils +import app.aaps.pump.equil.data.RunMode +import app.aaps.pump.equil.databinding.EquilFraBinding +import app.aaps.pump.equil.events.EventEquilDataChanged +import app.aaps.pump.equil.events.EventEquilModeChanged +import app.aaps.pump.equil.manager.EquilManager +import app.aaps.pump.equil.manager.command.* +import app.aaps.pump.equil.ui.* +import app.aaps.pump.equil.ui.dlg.LoadingDlg +import app.aaps.pump.equil.ui.pair.EquilPairActivity +import dagger.android.support.DaggerFragment +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import java.time.Duration +import javax.inject.Inject + +class EquilFragment : DaggerFragment() { + + @Inject lateinit var rxBus: RxBus + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var sp: SP + @Inject lateinit var warnColors: WarnColors + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var equilPumpPlugin: EquilPumpPlugin + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var uel: UserEntryLogger + @Inject lateinit var equilManager: EquilManager + + private var disposable: CompositeDisposable = CompositeDisposable() + + private val handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) + private lateinit var refreshLoop: Runnable + + private var _binding: EquilFraBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + + init { + refreshLoop = Runnable { + activity?.runOnUiThread { updateGUI() } + handler.postDelayed(refreshLoop, T.mins(1).msecs()) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _binding = EquilFraBinding.inflate(inflater, container, false) + return binding.root + } + + @Synchronized + override fun onResume() { + super.onResume() + handler.postDelayed(refreshLoop, T.mins(1).msecs()) + + disposable += rxBus + .toObservable(EventPumpStatusChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventEquilDataChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateGUI() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventEquilModeChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ updateModel() }, fabricPrivacy::logException) + + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + handler.removeCallbacks(refreshLoop) + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + @SuppressLint("SetTextI18n") + @Synchronized + fun updateGUI() { + if (_binding == null) return + val devName = equilManager.serialNumber + if (!TextUtils.isEmpty(devName)) { + binding.battery.text = equilManager.battery.toString() + "%" + binding.insulinReservoir.text = equilManager.currentInsulin.toString() + binding.basalSpeed.text = String.format(rh.gs(R.string.equil_unit_u_hours), equilPumpPlugin.baseBasalRate) + binding.firmwareVersion.text = equilManager.firmwareVersion?.toString() + equilManager.startInsulin.let { + if (it == -1) { + binding.totalDelivered.text = "-" + } else { + val totalDelivered = it - equilManager.currentInsulin + binding.totalDelivered.text = String.format( + rh.gs(R.string.equil_unit_u), + totalDelivered.toString() + ) + } + } + + binding.timeDevice.text = dateUtil.dateAndTimeAndSecondsString(equilManager.lastDataTime) + val runMode = equilManager.runMode + binding.mode.setTextColor(Color.WHITE) + if (equilManager.isActivationCompleted) { + when (runMode) { + RunMode.RUN -> { + binding.mode.text = rh.gs(R.string.equil_mode_running) + binding.btnResumeDelivery.visibility = View.GONE + binding.btnSuspendDelivery.visibility = View.VISIBLE + } + + RunMode.STOP -> { + binding.mode.text = rh.gs(R.string.equil_mode_stopped) + binding.btnResumeDelivery.visibility = View.GONE + binding.btnSuspendDelivery.visibility = View.GONE + } + + RunMode.SUSPEND -> { + binding.mode.text = rh.gs(R.string.equil_mode_suspended) + binding.btnResumeDelivery.visibility = View.VISIBLE + binding.btnSuspendDelivery.visibility = View.GONE + } + + else -> { + binding.btnResumeDelivery.visibility = View.VISIBLE + binding.btnSuspendDelivery.visibility = View.GONE + binding.mode.text = "" + } + } + } else { + binding.btnResumeDelivery.visibility = View.GONE + binding.btnSuspendDelivery.visibility = View.GONE + binding.mode.text = rh.gs(R.string.equil_init_insulin_error) + binding.mode.setTextColor(Color.RED) + } + + binding.serialNumber.text = devName + updateTempBasal() + runOnUiThread { + equilManager.bolusRecord.let { + if (it == null) { + binding.lastBolus.text = "-" + } else { + val text = + rh.gs( + R.string.equil_common_overview_last_bolus_value, + it.amount, + rh.gs(app.aaps.core.ui.R.string.insulin_unit_shortname), + readableDuration(Duration.ofMillis(System.currentTimeMillis() - it.startTime)) + ) + binding.lastBolus.text = text + } + + } + } + + binding.btnBind.visibility = View.GONE + binding.btnOpDressing.visibility = View.VISIBLE + binding.btnUnbind.visibility = View.VISIBLE + binding.btnHistory.visibility = View.VISIBLE + + } else { + binding.tempBasal.text = "-" + binding.lastBolus.text = '-'.toString() + binding.battery.text = '-'.toString() + binding.basalSpeed.text = '-'.toString() + binding.timeDevice.text = '-'.toString() + binding.mode.text = '-'.toString() + binding.serialNumber.text = "-" + binding.firmwareVersion.text = "-" + binding.insulinReservoir.text = "-" + binding.totalDelivered.text = "-" + binding.btnBind.visibility = View.VISIBLE + binding.btnOpDressing.visibility = View.GONE + binding.btnUnbind.visibility = View.GONE + binding.btnHistory.visibility = View.GONE + binding.btnResumeDelivery.visibility = View.GONE + binding.btnSuspendDelivery.visibility = View.GONE + binding.btnBind.setOnClickListener { + val intent = Intent(context, EquilPairActivity::class.java) + intent.putExtra(EquilPairActivity.KEY_TYPE, EquilPairActivity.Type.PAIR) + startActivity(intent) + } + } + binding.imv.setOnClickListener { + // commandQueue.customCommand(CmdStatusGet(), null) + } + binding.progressDeviceTime.visibility = View.GONE + binding.btnHistory.setOnClickListener { + if (TextUtils.isEmpty(devName)) { + ToastUtils.errorToast(context, rh.gs(R.string.equil_error_no_devices)) + } else { + startActivity(Intent(context, EquilHistoryRecordActivity::class.java)) + } + } + + binding.btnResumeDelivery.setOnClickListener { + showSetModeDialog() + } + binding.btnSuspendDelivery.setOnClickListener { + showSetModeDialog() + } + binding.btnUnbind.setOnClickListener { + if (TextUtils.isEmpty(devName)) { + ToastUtils.errorToast(context, rh.gs(R.string.equil_error_no_devices)) + } else + startActivity(Intent(context, EquilUnPairDetachActivity::class.java)) + + } + binding.btnOpDressing.setOnClickListener { + if (TextUtils.isEmpty(devName)) { + ToastUtils.errorToast(context, rh.gs(R.string.equil_error_no_devices)) + } else + changeInsulin() + + } + } + + + private fun showSetModeDialog() { + val runMode = equilManager.runMode + var tempMode = RunMode.RUN + if (runMode == RunMode.RUN) { + tempMode = RunMode.SUSPEND + } + showLoading() + commandQueue.customCommand(CmdModelSet(tempMode.command), object : Callback() { + override fun run() { + dismissLoading() + aapsLogger.debug(LTag.PUMPCOMM, "result====" + result.success) + if (result.success) { + equilManager.runMode = tempMode + runOnUiThread { updateGUI() } + } + } + }) + + } + + private fun updateTempBasal() { + val tempBasal = equilManager.tempBasal + if (tempBasal != null && equilManager.isTempBasalRunning) { + val startTime = tempBasal.startTime + val rate = tempBasal.rate + val duration = tempBasal.duration / 60 / 1000 + val minutesRunning = Duration.ofMillis(System.currentTimeMillis() - startTime).toMinutes() + binding.tempBasal.text = rh.gs( + R.string.equil_common_overview_temp_basal_value, + rate, + dateUtil.timeString(startTime), + minutesRunning, + duration + ) + } else { + binding.tempBasal.text = "-" + } + } + + + + @SuppressLint("SetTextI18n") + @Synchronized + fun updateModel() { + // binding.mode.text = equilPumpPlugin.equilManager.equilServiceData.mode.toString() + } + + private fun showLoading() { + LoadingDlg().show(childFragmentManager, "loading") + } + + private fun dismissLoading() { + val fragment = childFragmentManager.findFragmentByTag("loading") + if (fragment is DialogFragment) { + fragment.dismiss() + } + } + + @SuppressLint("SetTextI18n") + @Synchronized + fun changeInsulin() { + + activity?.let { activity -> + context?.let { context -> + protectionCheck.queryProtection( + activity, ProtectionCheck.Protection.PREFERENCES, + UIRunnable { + val intent = Intent(context, EquilPairActivity::class.java) + intent.putExtra(EquilPairActivity.KEY_TYPE, EquilPairActivity.Type.CHANGE_INSULIN) + startActivity(intent) + } + ) + } + } + } + + private fun readableDuration(duration: Duration): String { + val hours = duration.toHours().toInt() + val minutes = duration.toMinutes().toInt() + val seconds = duration.seconds + when { + seconds < 10 -> return rh.gs(R.string.equil_common_moments_ago) + + seconds < 60 -> { + return rh.gs(R.string.equil_common_less_than_a_minute_ago) + } + + seconds < 60 * 60 -> { // < 1 hour + return rh.gs( + R.string.equil_common_time_ago, + rh.gq(R.plurals.equil_common_minutes, minutes, minutes) + ) + } + + seconds < 24 * 60 * 60 -> { // < 1 day + val minutesLeft = minutes % 60 + if (minutesLeft > 0) + return rh.gs( + R.string.equil_common_time_ago, + rh.gs( + R.string.equil_common_composite_time, + rh.gq(R.plurals.equil_common_hours, hours, hours), + rh.gq(R.plurals.equil_common_minutes, minutesLeft, minutesLeft) + ) + ) + return rh.gs( + R.string.equil_common_time_ago, + rh.gq(R.plurals.equil_common_hours, hours, hours) + ) + } + + else -> { + val days = hours / 24 + val hoursLeft = hours % 24 + if (hoursLeft > 0) + return rh.gs( + R.string.equil_common_time_ago, + rh.gs( + R.string.equil_common_composite_time, + rh.gq(R.plurals.equil_common_days, days, days), + rh.gq(R.plurals.equil_common_hours, hoursLeft, hoursLeft) + ) + ) + return rh.gs( + R.string.equil_common_time_ago, + rh.gq(R.plurals.equil_common_days, days, days) + ) + } + } + } + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/EquilPumpPlugin.kt b/pump/equil/src/main/java/app/aaps/pump/equil/EquilPumpPlugin.kt new file mode 100644 index 00000000000..c592da04fb6 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/EquilPumpPlugin.kt @@ -0,0 +1,478 @@ +package app.aaps.pump.equil + +import android.content.Context +import android.os.Handler +import android.os.HandlerThread +import android.text.format.DateFormat +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.plugin.PluginDescription +import app.aaps.core.interfaces.plugin.PluginType +import app.aaps.core.interfaces.profile.Profile +import app.aaps.core.interfaces.pump.DetailedBolusInfo +import app.aaps.core.interfaces.pump.Pump +import app.aaps.core.interfaces.pump.PumpEnactResult +import app.aaps.core.interfaces.pump.PumpPluginBase +import app.aaps.core.interfaces.pump.PumpSync +import app.aaps.core.interfaces.pump.PumpSync.TemporaryBasalType +import app.aaps.core.interfaces.pump.defs.ManufacturerType +import app.aaps.core.interfaces.pump.defs.PumpDescription +import app.aaps.core.interfaces.pump.defs.PumpType +import app.aaps.core.interfaces.queue.Callback +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.core.interfaces.queue.CustomCommand +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.AapsSchedulers +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.EventPreferenceChange +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.utils.DateUtil +import app.aaps.core.interfaces.utils.DecimalFormatter +import app.aaps.core.interfaces.utils.TimeChangeType +import app.aaps.core.interfaces.utils.fabric.FabricPrivacy +import app.aaps.core.ui.toast.ToastUtils +import app.aaps.pump.equil.data.AlarmMode +import app.aaps.pump.equil.data.BolusProfile +import app.aaps.pump.equil.data.RunMode +import app.aaps.pump.equil.driver.definition.ActivationProgress +import app.aaps.pump.equil.driver.definition.BasalSchedule +import app.aaps.pump.equil.events.EventEquilDataChanged +import app.aaps.pump.equil.manager.EquilManager +import app.aaps.pump.equil.manager.command.BaseCmd +import app.aaps.pump.equil.manager.command.CmdAlarmSet +import app.aaps.pump.equil.manager.command.CmdBasalSet +import app.aaps.pump.equil.manager.command.CmdSettingSet +import app.aaps.pump.equil.manager.command.CmdStatusGet +import app.aaps.pump.equil.manager.command.CmdTimeSet +import app.aaps.pump.equil.manager.command.PumpEvent +import dagger.android.HasAndroidInjector +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import org.joda.time.DateTime +import org.joda.time.Duration +import org.json.JSONException +import org.json.JSONObject +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton class EquilPumpPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + rh: ResourceHelper, + commandQueue: CommandQueue, + private val aapsSchedulers: AapsSchedulers, + private val rxBus: RxBus, + private val context: Context, + private val sp: SP, + private val fabricPrivacy: FabricPrivacy, + private val dateUtil: DateUtil, + private val pumpSync: PumpSync, + private val equilManager: EquilManager, + private val decimalFormatter: DecimalFormatter +) : PumpPluginBase( + PluginDescription() + .mainType(PluginType.PUMP) + .fragmentClass(EquilFragment::class.java.name) + .pluginIcon(app.aaps.core.ui.R.drawable.ic_equil_128) + .pluginName(R.string.equil_name) + .shortName(R.string.equil_name_short) + .preferencesId(R.xml.pref_equil) + .description(R.string.equil_pump_description), injector, aapsLogger, rh, commandQueue +), Pump { + + override val pumpDescription: PumpDescription + private val pumpType = PumpType.EQUIL + private val bolusProfile: BolusProfile = BolusProfile() + + private val disposable = CompositeDisposable() + val handler = Handler(HandlerThread(this::class.java.simpleName + "Handler").also { it.start() }.looper) + private lateinit var statusChecker: Runnable + override fun onStart() { + super.onStart() + handler.postDelayed(statusChecker, STATUS_CHECK_INTERVAL_MILLIS) + disposable += rxBus + .toObservable(EventEquilDataChanged::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ playAlarm() }, fabricPrivacy::logException) + disposable += rxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(aapsSchedulers.io) + .subscribe({ event -> + if (event.isChanged(rh.gs(R.string.key_equil_tone))) { + val mode = AlarmMode.fromInt(sp.getString(R.string.key_equil_tone, "3").toInt()) + commandQueue.customCommand(CmdAlarmSet(mode.command), object : Callback() { + override fun run() { + if (result.success) ToastUtils.infoToast(context, rh.gs(R.string.equil_pump_updated)) + else ToastUtils.infoToast(context, rh.gs(R.string.equil_error)) + } + }) + } else if (event.isChanged(rh.gs(R.string.key_equil_maxbolus))) { + val data = sp.getDouble(R.string.key_equil_maxbolus, 10.0) + commandQueue.customCommand(CmdSettingSet(data), object : Callback() { + override fun run() { + if (result.success) ToastUtils.infoToast(context, rh.gs(R.string.equil_pump_updated)) + else ToastUtils.infoToast(context, rh.gs(R.string.equil_error)) + } + }) + } + }, fabricPrivacy::logException) + } + + var tempActivationProgress = ActivationProgress.NONE + + init { + pumpDescription = PumpDescription(pumpType) + statusChecker = Runnable { + if (commandQueue.size() == 0 && commandQueue.performing() == null) { + if (equilManager.isActivationCompleted) commandQueue.customCommand(CmdStatusGet(), null) + } else { + aapsLogger.debug(LTag.PUMPCOMM, "Skipping Pod status check because command queue is not empty") + } + handler.postDelayed(statusChecker, STATUS_CHECK_INTERVAL_MILLIS) + } + PumpEvent.init(rh) + } + + override fun onStop() { + super.onStop() + aapsLogger.debug(LTag.PUMPCOMM, "EquilPumpPlugin.onStop()") + handler.removeCallbacksAndMessages(null) + disposable.clear() + } + + override fun isInitialized(): Boolean = true + override fun isConnected(): Boolean = true + override fun isConnecting(): Boolean = false + override fun isBusy(): Boolean = false + + override fun isHandshakeInProgress(): Boolean = false + override fun connect(reason: String) {} + + override fun isSuspended(): Boolean { + val runMode = equilManager.runMode + return if (equilManager.isActivationCompleted) { + runMode == RunMode.SUSPEND || runMode == RunMode.STOP + } else true + } + + override fun getPumpStatus(reason: String) {} + override fun setNewBasalProfile(profile: Profile): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "setNewBasalProfile") + val mode = equilManager.runMode + if (mode === RunMode.RUN || mode === RunMode.SUSPEND) { + val basalSchedule = BasalSchedule.mapProfileToBasalSchedule(profile) + val pumpEnactResult = equilManager.executeCmd(CmdBasalSet(basalSchedule, profile)) + if (pumpEnactResult.success) { + equilManager.basalSchedule = basalSchedule + } + return pumpEnactResult + } + return PumpEnactResult(injector).enacted(false).success(false).comment(rh.gs(R.string.equil_pump_not_run)) + } + + override fun isThisProfileSet(profile: Profile): Boolean { + return if (!equilManager.isActivationCompleted) { + // When no Pod is active, return true here in order to prevent AAPS from setting a profile + // When we activate a new Pod, we just use ProfileFunction to set the currently active profile + true + } else equilManager.basalSchedule == BasalSchedule.mapProfileToBasalSchedule(profile) + } + + override fun lastDataTime(): Long { + aapsLogger.debug(LTag.PUMPCOMM, "lastDataTime: ${dateUtil.dateAndTimeAndSecondsString(equilManager.lastDataTime)}") + return equilManager.lastDataTime + } + + override val baseBasalRate: Double + get() = if (isSuspended()) 0.0 else equilManager.basalSchedule?.rateAt(toDuration(DateTime.now())) ?: 0.0 + override val reservoirLevel: Double + get() = equilManager.currentInsulin.toDouble() + override val batteryLevel: Int + get() = equilManager.battery + + override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { + if (detailedBolusInfo.insulin == 0.0) { + // bolus requested + aapsLogger.error("deliverTreatment: Invalid input: neither carbs nor insulin are set in treatment") + return PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0).comment("Invalid input") + } + val maxBolus = sp.getDouble(R.string.key_equil_maxbolus, 10.0) + if (detailedBolusInfo.insulin > maxBolus) { + val formattedValue = "%.2f".format(maxBolus) + val comment = rh.gs(R.string.equil_maxbolus_tips, formattedValue) + return PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0).comment(comment) + + } + val mode = equilManager.runMode + if (mode !== RunMode.RUN) { + return PumpEnactResult(injector).enacted(false).success(false).bolusDelivered(0.0).comment(rh.gs(R.string.equil_pump_not_run)) + } + val lastInsulin = equilManager.currentInsulin + return if (detailedBolusInfo.insulin > lastInsulin) { + PumpEnactResult(injector).success(false).enacted(false).bolusDelivered(0.0).comment(R.string.equil_not_enough_insulin) + } else deliverBolus(detailedBolusInfo) + } + + override fun stopBolusDelivering() { + equilManager.stopBolus(bolusProfile) + aapsLogger.debug(LTag.PUMPCOMM, "stopBolusDelivering=====") + } + + override fun setTempBasalAbsolute( + absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType + ): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "setTempBasalAbsolute=====$absoluteRate====$durationInMinutes===$enforceNew") + if (durationInMinutes <= 0 || durationInMinutes % BASAL_STEP_DURATION.standardMinutes != 0L) { + return PumpEnactResult(injector).success(false).comment(rh.gs(R.string.equil_error_set_temp_basal_failed_validation, BASAL_STEP_DURATION.standardMinutes)) + } + val mode = equilManager.runMode + if (mode !== RunMode.RUN) { + return PumpEnactResult(injector).enacted(false).success(false).comment(rh.gs(R.string.equil_pump_not_run)) + } + var pumpEnactResult = PumpEnactResult(injector) + pumpEnactResult.success(false) + pumpEnactResult = equilManager.tempBasalPump + if (pumpEnactResult.success) { + if (pumpEnactResult.enacted) { + pumpEnactResult = cancelTempBasal(true) + } + if (pumpEnactResult.success) { + pumpEnactResult = equilManager.setTempBasal( + absoluteRate, durationInMinutes, false + ) + if (pumpEnactResult.success) { + pumpEnactResult.isTempCancel = false + pumpEnactResult.duration = durationInMinutes + pumpEnactResult.isPercent = false + pumpEnactResult.absolute = absoluteRate + } + } + } + return pumpEnactResult + } + + override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "cancelTempBasal=====$enforceNew") + val pumpEnactResult = equilManager.setTempBasal(0.0, 0, true) + if (pumpEnactResult.success) { + pumpEnactResult.isTempCancel = true + } + return pumpEnactResult + } + + override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject { + if (!isConnected()) return JSONObject().put("status", JSONObject().put("status", "no active Pod")) + + val json = JSONObject() + val battery = JSONObject() + val status = JSONObject() + val extended = JSONObject() + return try { + battery.put("percent", batteryLevel) + status.put("status", if (isSuspended()) "suspended" else "normal") + status.put("timestamp", dateUtil.toISOString(lastDataTime())) + extended.put("Version", version) + pumpSync.expectedPumpState().bolus?.let { bolus -> + extended.put("LastBolus", dateUtil.dateAndTimeString(bolus.timestamp)) + extended.put("LastBolusAmount", bolus.amount) + } + pumpSync.expectedPumpState().temporaryBasal?.let { temporaryBasal -> + extended.put("TempBasalAbsoluteRate", temporaryBasal.convertedToAbsolute(dateUtil.now(), profile)) + extended.put("TempBasalStart", dateUtil.dateAndTimeString(temporaryBasal.timestamp)) + extended.put("TempBasalRemaining", temporaryBasal.plannedRemainingMinutes) + } + pumpSync.expectedPumpState().extendedBolus?.let { extendedBolus -> + extended.put("ExtendedBolusAbsoluteRate", extendedBolus.rate) + extended.put("ExtendedBolusStart", dateUtil.dateAndTimeString(extendedBolus.timestamp)) + extended.put("ExtendedBolusRemaining", extendedBolus.plannedRemainingMinutes) + } + extended.put("BaseBasalRate", baseBasalRate) + extended.put("ActiveProfile", profileName) + json.put("battery", battery) + json.put("status", status) + json.put("extended", extended) + json.put("reservoir", reservoirLevel) + json.put("clock", dateUtil.toISOString(dateUtil.now())) + json + } catch (e: JSONException) { + json.put("status", JSONObject().put("status", "error" + e.message)) + aapsLogger.error("Unhandled exception", e) + json + } + } + + override fun manufacturer(): ManufacturerType = ManufacturerType.Equil + override fun model(): PumpType = PumpType.EQUIL + override fun serialNumber(): String = equilManager.serialNumber + + override fun shortStatus(veryShort: Boolean): String { + if (!equilManager.isActivationCompleted) { + return rh.gs(R.string.equil_init_insulin_error) + } + var ret = "" + if (lastDataTime() != 0L) { + val agoMsec = System.currentTimeMillis() - lastDataTime() + val agoMin = (agoMsec / 60.0 / 1000.0).toInt() + ret += rh.gs(R.string.equil_common_short_status_last_connection, agoMin) + "\n" + } + if (equilManager.bolusRecord != null) { + ret += rh.gs( + R.string.equil_common_short_status_last_bolus, decimalFormatter.to2Decimal(equilManager.bolusRecord.amount), DateFormat.format( + "HH:mm", equilManager.bolusRecord.startTime + ) + ) + "\n" + } + val (temporaryBasal, extendedBolus, _, profile) = pumpSync.expectedPumpState() + if (temporaryBasal != null && profile != null) { + ret += rh.gs(R.string.equil_common_short_status_temp_basal, temporaryBasal.toStringFull(dateUtil, decimalFormatter) + "\n") + } + if (extendedBolus != null) { + ret += rh.gs(R.string.equil_common_short_status_extended_bolus, extendedBolus.toStringFull(dateUtil, decimalFormatter) + "\n") + } + ret += rh.gs(R.string.equil_common_short_status_reservoir, reservoirLevel) + return ret.trim { it <= ' ' } + } + + override fun executeCustomCommand(customCommand: CustomCommand): PumpEnactResult? { + aapsLogger.debug(LTag.PUMPCOMM, "executeCustomCommand $customCommand") + var pumpEnactResult: PumpEnactResult? = null + + if (customCommand is BaseCmd) { + pumpEnactResult = equilManager.executeCmd(customCommand) + } + if (customCommand is CmdStatusGet) { + pumpEnactResult = equilManager.readEquilStatus() + } + return pumpEnactResult + } + + override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) { + aapsLogger.debug(LTag.PUMP, "DST and/or TimeZone changed event will be consumed by driver") + commandQueue.customCommand(CmdTimeSet(), null) + } + + override val isFakingTempsByExtendedBoluses: Boolean = false + override fun canHandleDST(): Boolean = false + override fun disconnect(reason: String) { + aapsLogger.info(LTag.PUMPCOMM, "disconnect reason=$reason") + equilManager.closeBleAuto() + } + + override fun stopConnecting() {} + + override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "setTempBasalPercent $percent $durationInMinutes ") + return if (percent == 0) { + setTempBasalAbsolute(0.0, durationInMinutes, profile, enforceNew, tbrType) + } else { + var absoluteValue = profile.getBasal() * (percent / 100.0) + absoluteValue = pumpDescription.pumpType.determineCorrectBasalSize(absoluteValue) + setTempBasalAbsolute(absoluteValue, durationInMinutes, profile, enforceNew, tbrType) + } + } + + override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "setExtendedBolus $insulin, $durationInMinutes") + val pumpEnactResult = equilManager.setExtendedBolus(insulin, durationInMinutes, false) + if (pumpEnactResult.success) { + pumpEnactResult.isTempCancel = false + pumpEnactResult.duration = durationInMinutes + pumpEnactResult.isPercent = false + pumpEnactResult.absolute = insulin + } + return pumpEnactResult + } + + override fun cancelExtendedBolus(): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "cancelExtendedBolus") + return equilManager.setExtendedBolus(0.0, 0, true) + } + + override fun loadTDDs(): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "loadTDDs") + return PumpEnactResult(injector).success(false).enacted(false) + } + + override fun isBatteryChangeLoggingEnabled(): Boolean = false + + private fun deliverBolus(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult { + aapsLogger.debug(LTag.PUMPCOMM, "deliverBolus") + bolusProfile.insulin = detailedBolusInfo.insulin + return equilManager.bolus(detailedBolusInfo, bolusProfile) + } + + fun showToast(s: String) { + ToastUtils.showToastInUiThread(context, s) + } + + fun resetData() { + sp.putBoolean(EquilConst.Prefs.Equil_ALARM_BATTERY_10, false) + sp.putBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_10, false) + sp.putBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_5, false) + sp.putBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_5, false) + sp.putBoolean(EquilConst.Prefs.EQUIL_BASAL_SET, false) + } + + fun clearData() { + resetData() + equilManager.clearPodState() + sp.putString(EquilConst.Prefs.EQUIL_DEVICES, "") + sp.putString(EquilConst.Prefs.EQUIL_PASSWORD, "") + } + + private fun playAlarm() { + val battery = equilManager.battery + val insulin = equilManager.currentInsulin + val alarmBattery = sp.getBoolean(EquilConst.Prefs.EQUIL_ALARM_BATTERY, true) + val alarmInsulin = sp.getBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN, true) + if (battery <= 10 && alarmBattery) { + val alarmBattery10 = sp.getBoolean(EquilConst.Prefs.Equil_ALARM_BATTERY_10, false) + if (!alarmBattery10) { + equilManager.showNotification( + Notification.FAILED_UPDATE_PROFILE, rh.gs(R.string.equil_low_battery) + battery + "%", Notification.NORMAL, app.aaps.core.ui.R.raw.alarm + ) + sp.putBoolean(EquilConst.Prefs.Equil_ALARM_BATTERY_10, true) + } else { + if (battery < 5) { + equilManager.showNotification( + Notification.FAILED_UPDATE_PROFILE, rh.gs(R.string.equil_low_battery) + battery + "%", Notification.URGENT, app.aaps.core.ui.R.raw.alarm + ) + } + } + } + if (equilManager.runMode === RunMode.RUN && alarmInsulin && equilManager.isActivationCompleted) { + when { + insulin in 6..10 -> { + val alarmInsulin10 = sp.getBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_10, false) + if (!alarmInsulin10) { + equilManager.showNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(R.string.equil_low_insulin) + insulin + "U", Notification.NORMAL, app.aaps.core.ui.R.raw.alarm) + sp.putBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_10, true) + } + } + + insulin in 3..5 -> { + val alarmInsulin5 = sp.getBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_5, false) + if (!alarmInsulin5) { + equilManager.showNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(R.string.equil_low_insulin) + insulin + "U", Notification.NORMAL, app.aaps.core.ui.R.raw.alarm) + sp.putBoolean(EquilConst.Prefs.EQUIL_ALARM_INSULIN_5, true) + } + } + + insulin <= 2 -> { + equilManager.showNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(R.string.equil_low_insulin) + insulin + "U", Notification.URGENT, app.aaps.core.ui.R.raw.alarm) + } + } + } + } + + companion object { + + private const val STATUS_CHECK_INTERVAL_MILLIS = 60 * 3000L // 1 minute + private val BASAL_STEP_DURATION: Duration = Duration.standardMinutes(30) + fun toDuration(dateTime: DateTime?): Duration { + requireNotNull(dateTime) { "dateTime can not be null" } + return Duration(dateTime.toLocalTime().millisOfDay.toLong()) + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ble/EquilBLE.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ble/EquilBLE.kt new file mode 100644 index 00000000000..b493156f53b --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ble/EquilBLE.kt @@ -0,0 +1,438 @@ +package app.aaps.pump.equil.ble + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattCallback +import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanFilter +import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings +import android.content.Context +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.os.Message +import android.os.SystemClock +import android.text.TextUtils +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.rx.events.EventPumpStatusChanged +import app.aaps.core.utils.notifyAll +import app.aaps.pump.equil.EquilConst +import app.aaps.pump.equil.ble.GattAttributes.characteristicConfigDescriptor +import app.aaps.pump.equil.database.ResolvedResult +import app.aaps.pump.equil.driver.definition.BluetoothConnectionState +import app.aaps.pump.equil.manager.EquilManager +import app.aaps.pump.equil.manager.EquilResponse +import app.aaps.pump.equil.manager.Utils +import app.aaps.pump.equil.manager.command.BaseCmd +import app.aaps.pump.equil.manager.command.CmdDevicesOldGet +import app.aaps.pump.equil.manager.command.CmdHistoryGet +import app.aaps.pump.equil.manager.command.CmdInsulinGet +import app.aaps.pump.equil.manager.command.CmdModelGet +import app.aaps.pump.equil.manager.command.CmdPair +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +@SuppressLint("MissingPermission") +@Singleton +class EquilBLE @Inject constructor( + private val aapsLogger: AAPSLogger, + private val context: Context, + private val rxBus: RxBus +) { + + var equilManager: EquilManager? = null + private var mGattCallback: BluetoothGattCallback? = null + var notifyChara: BluetoothGattCharacteristic? = null + var writeChara: BluetoothGattCharacteristic? = null + + private var bluetoothGatt: BluetoothGatt? = null + private val bluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter + var isConnected = false + var connecting = false + var macAddress: String? = null + private var bleHandler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper) + @Synchronized + fun unBond(transmitterMAC: String?) { + if (transmitterMAC == null) return + try { + val pairedDevices = bluetoothAdapter?.bondedDevices ?: return + if (pairedDevices.isNotEmpty()) { + for (device in pairedDevices) { + if (device.address == transmitterMAC) { + try { + val method = device.javaClass.getMethod("removeBond") + method.invoke(device) + } catch (e: Exception) { + aapsLogger.error(LTag.PUMPCOMM, "Error", e) + } + } + } + } + } catch (e: Exception) { + aapsLogger.error(LTag.PUMPCOMM, "Error", e) + } + } + + private fun bleConnectErrorForResult() { + baseCmd?.let { baseCmd -> + synchronized(baseCmd) { + baseCmd.isCmdStatus = false + baseCmd.notifyAll() + } + } + } + + @Suppress("deprecation", "OVERRIDE_DEPRECATION") + fun init(equilManager: EquilManager) { + macAddress = equilManager.address + this.equilManager = equilManager + aapsLogger.debug(LTag.PUMPBTCOMM, "initGatt======= ") + mGattCallback = object : BluetoothGattCallback() { + @Synchronized + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, i2: Int) { + super.onConnectionStateChange(gatt, status, i2) + val str = if (i2 == BluetoothProfile.STATE_CONNECTED) "CONNECTED" else "DISCONNECTED" + val sb = "onConnectionStateChange called with status:$status, state:$str, i2: $i2, error133: " + aapsLogger.debug(LTag.PUMPBTCOMM, "onConnectionStateChange $sb") + connecting = false + if (status == 133) { + unBond(macAddress) + SystemClock.sleep(50) + aapsLogger.debug(LTag.PUMPCOMM, "error133 ") + baseCmd?.setResolvedResult(ResolvedResult.CONNECT_ERROR) + bleConnectErrorForResult() + disconnect() + return + } + if (i2 == BluetoothProfile.STATE_CONNECTED) { + isConnected = true + equilManager.bluetoothConnectionState = BluetoothConnectionState.CONNECTED + handler.removeMessages(TIME_OUT_CONNECT_WHAT) + bluetoothGatt?.discoverServices() + updateCmdStatus(ResolvedResult.FAILURE) + // rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)); + } else if (i2 == BluetoothProfile.STATE_DISCONNECTED) { + bleConnectErrorForResult() + disconnect() + } + } + + @Synchronized + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + if (status != BluetoothGatt.GATT_SUCCESS) { + aapsLogger.debug(LTag.PUMPBTCOMM, "onServicesDiscovered received: $status") + return + } + val service = gatt.getService(UUID.fromString(GattAttributes.SERVICE_RADIO)) + if (service != null) { + notifyChara = service.getCharacteristic(UUID.fromString(GattAttributes.NRF_UART_NOTIFY)) + writeChara = service.getCharacteristic(UUID.fromString(GattAttributes.NRF_UART_WRITE)) + // rxBus.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)); + openNotification() + requestHighPriority() + } + } + + override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { + try { + SystemClock.sleep(EquilConst.EQUIL_BLE_WRITE_TIME_OUT) + writeData() + } catch (e: Exception) { + aapsLogger.error(LTag.PUMPBTCOMM, "Error", e) + } + } + + override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { + onCharacteristicChanged(gatt, characteristic) + } + + override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + requestHighPriority() + decode(characteristic.value) + } + + @Synchronized override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) { + aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite received: $status") + if (status == BluetoothGatt.GATT_SUCCESS) { + aapsLogger.debug(LTag.PUMPBTCOMM, "onDescriptorWrite: Wrote GATT Descriptor successfully.") + ready() + } + } + } + } + + @Suppress("deprecation") fun openNotification() { + aapsLogger.debug(LTag.PUMPBTCOMM, "openNotification: $isConnected") + val r0 = bluetoothGatt?.setCharacteristicNotification(notifyChara, true) + if (r0 == true) { + val descriptor = notifyChara?.getDescriptor(characteristicConfigDescriptor) + val v = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + descriptor?.setValue(v) + val flag = bluetoothGatt?.writeDescriptor(descriptor) + aapsLogger.debug(LTag.PUMPBTCOMM, "openNotification: $flag") + } + } + + @SuppressLint("MissingPermission") + fun requestHighPriority() { + bluetoothGatt?.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH) + } + + fun ready() { + aapsLogger.debug(LTag.PUMPBTCOMM, "ready: ===$baseCmd") + dataList = ArrayList() + baseCmd?.let { baseCmd -> + equilResponse = baseCmd.equilResponse + indexData = 0 + writeData() + } + } + + private fun nextCmd2() { + dataList = ArrayList() + aapsLogger.debug(LTag.PUMPCOMM, "nextCmd===== ${baseCmd?.isEnd}====") + baseCmd?.let { baseCmd -> + equilResponse = baseCmd.nextEquilResponse + aapsLogger.debug(LTag.PUMPCOMM, "nextCmd===== $baseCmd===${equilResponse?.send}") + if ((equilResponse?.send?.size ?: 0) == 0) { + aapsLogger.debug(LTag.PUMPCOMM, "equilResponse is null") + return + } + indexData = 0 + writeData() + } + } + + fun disconnect() { + isConnected = false + startTrue = false + autoScan = false + equilManager?.bluetoothConnectionState = BluetoothConnectionState.DISCONNECTED + aapsLogger.debug(LTag.PUMPBTCOMM, "Closing GATT connection") + bluetoothGatt?.disconnect() + bluetoothGatt?.close() + bluetoothGatt = null + baseCmd = null + preCmd = null + rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)) + } + fun closeBleAuto(){ + handler.postDelayed({ + disconnect() + }, EquilConst.EQUIL_BLE_NEXT_CMD) + } + var autoScan = false + private fun findEquil(mac: String) { + if (mac.isEmpty()) return + if (isConnected) return + val equilDevice: BluetoothDevice? = bluetoothAdapter?.getRemoteDevice(mac) + if (autoScan) startScan() + else connectEquil(equilDevice) + } + + fun connectEquil(device: BluetoothDevice?) { +// disconnect(); + handler.postDelayed({ + if (device != null) { + aapsLogger.debug(LTag.PUMPCOMM, "connectEquil======") + bluetoothGatt = device.connectGatt(context, false, mGattCallback, BluetoothDevice.TRANSPORT_LE) + connecting = true + } + }, 500) + } + + private var baseCmd: BaseCmd? = null + private var preCmd: BaseCmd? = null + fun writeCmd(baseCmd: BaseCmd) { + aapsLogger.debug(LTag.PUMPCOMM, "writeCmd {}", baseCmd) + this.baseCmd = baseCmd + val mac: String = when (baseCmd) { + is CmdPair -> baseCmd.getAddress() + is CmdDevicesOldGet -> baseCmd.getAddress() + else -> equilManager?.address ?: error("Unknown MAC address") + } + autoScan = baseCmd is CmdModelGet || baseCmd is CmdInsulinGet + if (isConnected && baseCmd.isPairStep) { + ready() + } else if (isConnected) { + preCmd?.let { preCmd -> + baseCmd.setRunCode(preCmd.getRunCode()) + baseCmd.setRunPwd(preCmd.getRunPwd()) + nextCmd2() + } + } else { + findEquil(mac) + handler.sendEmptyMessageDelayed(TIME_OUT_CONNECT_WHAT, baseCmd.connectTimeOut.toLong()) + } + preCmd = baseCmd + } + + fun readHistory(baseCmd: CmdHistoryGet) { + if (isConnected && preCmd != null) { + baseCmd.setRunCode(preCmd!!.getRunCode()) + baseCmd.setRunPwd(preCmd!!.getRunPwd()) + this.baseCmd = baseCmd + nextCmd2() + preCmd = baseCmd + } else { + aapsLogger.debug(LTag.PUMPCOMM, "readHistory error") + } + } + + private var equilResponse: EquilResponse? = null + private var indexData = 0 + fun writeData() { + equilResponse?.let { equilResponse -> + val diff = System.currentTimeMillis() - equilResponse.cmdCreateTime + if (diff < EquilConst.EQUIL_CMD_TIME_OUT) { + if (indexData < equilResponse.send.size) { + val data = equilResponse.send[indexData].array() + write(data) + indexData++ + } else aapsLogger.debug(LTag.PUMPCOMM, "indexData error ") + } else aapsLogger.debug(LTag.PUMPCOMM, "equil cmd time out ") + } + } + + @Suppress("deprecation") + private fun write(bytes: ByteArray) { + if (writeChara == null || bluetoothGatt == null) { + aapsLogger.debug(LTag.PUMPCOMM, "write disconnect ") + disconnect() + return + } + writeChara?.setValue(bytes) + aapsLogger.debug(LTag.PUMPBTCOMM, "write: ${Utils.bytesToHex(bytes)}") + bluetoothGatt?.writeCharacteristic(writeChara) + } + + private var dataList: List = ArrayList() + @Synchronized fun decode(buffer: ByteArray?) { + val str = Utils.bytesToHex(buffer) + aapsLogger.debug(LTag.PUMPBTCOMM, "decode=====$str") + val response = baseCmd?.decodeEquilPacket(buffer) + if (response != null) { + writeConf(response) + dataList = ArrayList() + } + } + + private fun writeConf(equilResponse: EquilResponse?) { + try { + dataList = ArrayList() + this.equilResponse = equilResponse + indexData = 0 + writeData() + } catch (e: Exception) { + e.printStackTrace() + } + } + + var handler: Handler = object : Handler(Looper.getMainLooper()) { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + when (msg.what) { + TIME_OUT_WHAT -> stopScan() + + TIME_OUT_CONNECT_WHAT -> { + stopScan() + aapsLogger.debug(LTag.PUMPCOMM, "TIME_OUT_CONNECT_WHAT====") + baseCmd?.setResolvedResult(ResolvedResult.CONNECT_ERROR) + bleConnectErrorForResult() + disconnect() + } + } + } + } + private var startTrue = false + private fun startScan() { + macAddress = equilManager?.address + if (macAddress.isNullOrEmpty()) return + aapsLogger.debug(LTag.PUMPCOMM, "startScan====$startTrue====$macAddress===") + if (startTrue) { + return + } + val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner + if (bluetoothLeScanner != null) { + updateCmdStatus(ResolvedResult.NOT_FOUNT) + bluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), scanCallback) + } + } + + private fun updateCmdStatus(result: ResolvedResult) { + baseCmd?.setResolvedResult(result) + } + + val equilStatus: Unit + get() { + aapsLogger.debug(LTag.PUMPCOMM, "getEquilStatus====$startTrue====$isConnected") + if (startTrue || isConnected) { + return + } + autoScan = false + baseCmd = null + startScan() + } + + private fun buildScanFilters(): List { + val scanFilterList = ArrayList() + if (TextUtils.isEmpty(macAddress)) { + return scanFilterList + } + val scanFilterBuilder = ScanFilter.Builder() + scanFilterBuilder.setDeviceAddress(macAddress) + scanFilterList.add(scanFilterBuilder.build()) + return scanFilterList + } + + private fun buildScanSettings(): ScanSettings { + val builder = ScanSettings.Builder() + builder.setReportDelay(0) + return builder.build() + } + + private var scanCallback: ScanCallback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + super.onScanResult(callbackType, result) + val name = result.device.name + if (name.isNotEmpty()) { + try { + bleHandler.post { equilManager?.decodeData(result.scanRecord!!.bytes) } + stopScan() + if (autoScan) { + updateCmdStatus(ResolvedResult.CONNECT_ERROR) + connectEquil(result.device) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + } + + fun stopScan() { + startTrue = false + handler.removeMessages(TIME_OUT_WHAT) + val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner + if (isBluetoothAvailable) bluetoothLeScanner?.stopScan(scanCallback) + } + + private val isBluetoothAvailable: Boolean + get() = bluetoothAdapter?.isEnabled == true && bluetoothAdapter?.state == BluetoothAdapter.STATE_ON + + companion object { + + const val TIME_OUT_WHAT = 0x12 + const val TIME_OUT_CONNECT_WHAT = 0x13 + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ble/GattAttributes.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ble/GattAttributes.kt new file mode 100644 index 00000000000..46268a212cd --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ble/GattAttributes.kt @@ -0,0 +1,11 @@ +package app.aaps.pump.equil.ble + +import java.util.UUID + +object GattAttributes { + + const val SERVICE_RADIO = "0000f000-0000-1000-8000-00805f9b34fb" + const val NRF_UART_NOTIFY = "0000f001-0000-1000-8000-00805f9b34fb" + const val NRF_UART_WRITE = "0000f001-0000-1000-8000-00805f9b34fb" + val characteristicConfigDescriptor: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/data/AlarmMode.kt b/pump/equil/src/main/java/app/aaps/pump/equil/data/AlarmMode.kt new file mode 100644 index 00000000000..3cfdb7bfcad --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/data/AlarmMode.kt @@ -0,0 +1,12 @@ +package app.aaps.pump.equil.data + +enum class AlarmMode(val command: Int) { + MUTE(0), + TONE(1), + SHAKE(2), + TONE_AND_SHAKE(3); + + companion object { + fun fromInt(number: Int) = entries.firstOrNull { it.command == number } ?: TONE_AND_SHAKE + } +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/data/BolusProfile.kt b/pump/equil/src/main/java/app/aaps/pump/equil/data/BolusProfile.kt new file mode 100644 index 00000000000..c41e94d58c5 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/data/BolusProfile.kt @@ -0,0 +1,8 @@ +package app.aaps.pump.equil.data + + +class BolusProfile { + var timestamp: Long = 0 + var stop: Boolean = false + var insulin: Double = 0.0 +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/data/RunMode.kt b/pump/equil/src/main/java/app/aaps/pump/equil/data/RunMode.kt new file mode 100644 index 00000000000..4cabd59e68f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/data/RunMode.kt @@ -0,0 +1,8 @@ +package app.aaps.pump.equil.data + +enum class RunMode(val command: Int) { + RUN(1), + STOP(2), + SUSPEND(0), + NONE(-1) +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/Converters.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/Converters.kt new file mode 100644 index 00000000000..01147ac84e6 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/Converters.kt @@ -0,0 +1,27 @@ +package app.aaps.pump.equil.database + +import androidx.room.TypeConverter +import app.aaps.core.interfaces.profile.Profile +import com.google.gson.GsonBuilder + +class Converters { + + @TypeConverter + fun toBolusType(s: String) = enumValueOf(s) + + @TypeConverter + fun fromBolusType(bolusType: BolusType) = bolusType.name + + @TypeConverter + fun toSegments(s: String?): List { + s ?: return emptyList() + val gson = GsonBuilder().create() + return gson.fromJson(s, Array::class.java).toList() + } + + @TypeConverter + fun fromBasalValues(segments: List): String { + val gson = GsonBuilder().create() + return gson.toJson(segments) + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryDatabase.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryDatabase.kt new file mode 100644 index 00000000000..099952d6a65 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryDatabase.kt @@ -0,0 +1,31 @@ +package app.aaps.pump.equil.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +const val TABLE_EQUIL_HISTORY_PUMP = "equilHistoryPump" +const val TABLE_EQUIL_HISTORY_RECORD = "equilHistoryRecord" + +@Database( + entities = [EquilHistoryPump::class, EquilHistoryRecord::class], + exportSchema = true, + version = EquilHistoryDatabase.VERSION +) +@TypeConverters(Converters::class) +abstract class EquilHistoryDatabase : RoomDatabase() { + + abstract fun historyRecordDao(): EquilHistoryRecordDao + abstract fun historyPumpDao(): EquilHistoryPumpDao + + companion object { + + const val VERSION = 11 + fun build(context: Context) = + Room.databaseBuilder(context.applicationContext, EquilHistoryDatabase::class.java, "equil_database.db") + .fallbackToDestructiveMigration() + .build() + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPump.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPump.kt new file mode 100644 index 00000000000..6c9b287e795 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPump.kt @@ -0,0 +1,29 @@ +package app.aaps.pump.equil.database + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = TABLE_EQUIL_HISTORY_PUMP, + indices = [Index("code", "timestamp", "eventTimestamp", "eventIndex")] +) +class EquilHistoryPump { + + @PrimaryKey(autoGenerate = true) + var id: Long = 0 + var timestamp: Long = 0 + var code: Int = 0 + var battery: Int = 0 + var insulin: Int = 0 + var rate: Int = 0 + var largeRate: Int = 0 + var type: Int = 0 + var eventIndex: Int = 0 + var level: Int = 0 + var parm: Int = 0 + var port: Int = 0 + var eventTimestamp: Long = 0 + var serialNumber: String = "" + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPumpDao.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPumpDao.kt new file mode 100644 index 00000000000..0abea40d220 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryPumpDao.kt @@ -0,0 +1,32 @@ +package app.aaps.pump.equil.database + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.rxjava3.core.Single + +@Dao +abstract class EquilHistoryPumpDao { + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_PUMP WHERE timestamp >= :timestamp ORDER BY eventTimestamp DESC") + abstract fun allFromByType(timestamp: Long): Single> + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_PUMP WHERE serialNumber=:serialNumber and eventTimestamp >= :startTime and eventTimestamp<=:endTime ORDER BY eventTimestamp DESC") + abstract fun allFromByType(startTime: Long, endTime: Long, serialNumber: String): Single> + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_PUMP WHERE eventTimestamp = :timestamp AND eventIndex = :eventIndex LIMIT 0,1") + abstract fun findByIndexAndEventTime(timestamp: Long, eventIndex: Int): EquilHistoryPump + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_PUMP WHERE eventIndex = :eventIndex LIMIT 0,1") + abstract fun findByEventIndex(eventIndex: Int): EquilHistoryPump + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_PUMP WHERE serialNumber=:serialNumber ORDER BY eventTimestamp DESC LIMIT 0,1") + abstract fun last(serialNumber: String): EquilHistoryPump + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun createOrUpdate(danaHistoryRecord: EquilHistoryPump) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insert(danaHistoryRecord: EquilHistoryPump): Long +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecord.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecord.kt new file mode 100644 index 00000000000..151df240571 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecord.kt @@ -0,0 +1,64 @@ +package app.aaps.pump.equil.database + +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import app.aaps.pump.equil.R + +@Entity( + tableName = TABLE_EQUIL_HISTORY_RECORD, + indices = [Index("type", "timestamp")] +) +data class EquilHistoryRecord( + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + @Embedded(prefix = "tempBasalRecord_") var tempBasalRecord: EquilTempBasalRecord? = null, + @Embedded(prefix = "bolusRecord_") var bolusRecord: EquilBolusRecord? = null, + @Embedded(prefix = "basalprofile_") var basalValuesRecord: EquilBasalValuesRecord? = null, + var type: EventType? = null, + var timestamp: Long = 0, + var serialNumber: String, + var resolvedStatus: ResolvedResult? = null, + var resolvedAt: Long? = null, + var note: String? = null + +) { + + constructor( + type: EventType, eventTimestamp: Long, serialNumber: String + ) : this(0, null, null, null, type, eventTimestamp, serialNumber, null, null, null) + + constructor( + eventTimestamp: Long, serialNumber: String + ) : this(0, null, null, null, null, eventTimestamp, serialNumber, null, null, null) + + fun isSuccess(): Boolean = resolvedStatus == ResolvedResult.SUCCESS + + enum class EventType(val resourceId: Int) { + INITIALIZE_EQUIL(R.string.equil_common_cmd_pair), // First step of Pod activation + INSERT_CANNULA(R.string.equil_common_cmd_insert_cannula), // Second step of Pod activation + FILL(R.string.equil_common_cmd_fill), + SET_BASAL_PROFILE(R.string.equil_common_cmd_set_basal_schedule), // + SET_BOLUS(R.string.equil_common_cmd_set_bolus), // + CANCEL_BOLUS(R.string.equil_common_cmd_cancel_bolus), // + SET_EXTENDED_BOLUS(R.string.equil_common_cmd_set_extended_bolus), // + CANCEL_EXTENDED_BOLUS(R.string.equil_common_cmd_cancel_extended_bolus), // + SET_TEMPORARY_BASAL(R.string.equil_common_cmd_set_tbr), // + CANCEL_TEMPORARY_BASAL(R.string.equil_common_cmd_cancel_tbr), // + SET_TIME(R.string.equil_common_cmd_set_time), // + SUSPEND_DELIVERY(R.string.equil_common_cmd_suspend_delivery), + RESUME_DELIVERY(R.string.equil_common_cmd_resume_delivery), + UNPAIR_EQUIL(R.string.equil_common_cmd_unpair), + CHANGE_INSULIN(R.string.equil_common_change_insulin), + SET_ALARM_MUTE(R.string.equil_common_set_alarm_mute), + SET_ALARM_SHAKE(R.string.equil_common_set_alarm_shake), + SET_ALARM_TONE(R.string.equil_common_set_alarm_tone), + SET_ALARM_TONE_AND_SHAK(R.string.equil_common_set_alarm_tone_and_shake), + READ_DEVICES(R.string.equil_common_read_devices), + EQUIL_ALARM(R.string.equil_common_cmd_alarm) + } +} + + + diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecordDao.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecordDao.kt new file mode 100644 index 00000000000..defc6c44d36 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilHistoryRecordDao.kt @@ -0,0 +1,27 @@ +package app.aaps.pump.equil.database + +import androidx.room.* +import io.reactivex.rxjava3.core.Single + +@Dao +abstract class EquilHistoryRecordDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insert(equilHistoryRecord: EquilHistoryRecord): Long + + @Update + abstract fun update(equilHistoryRecord: EquilHistoryRecord): Int + + @Query("SELECT * FROM $TABLE_EQUIL_HISTORY_RECORD WHERE id = :id") + abstract fun getEquilHistoryRecordById(id: Long): EquilHistoryRecord? + + @Transaction + @Query("UPDATE $TABLE_EQUIL_HISTORY_RECORD SET resolvedStatus = :resolvedResult, resolvedAt = :resolvedAt WHERE id = :id ") + abstract fun markResolved(id: Long, resolvedResult: ResolvedResult, resolvedAt: Long) + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_RECORD WHERE type=:eventType ORDER BY timestamp DESC LIMIT 0,1") + abstract fun lastRecord(eventType: EquilHistoryRecord.EventType): EquilHistoryRecord + + @Query("SELECT * from $TABLE_EQUIL_HISTORY_RECORD WHERE timestamp >= :startTime and timestamp<=:endTime ORDER BY timestamp DESC") + abstract fun allSince(startTime: Long, endTime: Long): Single> +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilRecord.kt b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilRecord.kt new file mode 100644 index 00000000000..e6154aad829 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/database/EquilRecord.kt @@ -0,0 +1,13 @@ +package app.aaps.pump.equil.database + +import app.aaps.core.interfaces.profile.Profile + +sealed class EquilRecord + +data class EquilBolusRecord(val amount: Double, val bolusType: BolusType, var startTime: Long) : EquilRecord() + +data class EquilTempBasalRecord(val duration: Int, val rate: Double, var startTime: Long) : EquilRecord() + +data class EquilBasalValuesRecord(val segments: List) : EquilRecord() +enum class ResolvedResult { NONE, SUCCESS, FAILURE, CONNECT_ERROR, NOT_FOUNT } +enum class BolusType { DEFAULT, SMB; } diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilActivitiesModule.kt b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilActivitiesModule.kt new file mode 100644 index 00000000000..0c9f590d782 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilActivitiesModule.kt @@ -0,0 +1,48 @@ +package app.aaps.pump.equil.di + +import app.aaps.pump.equil.EquilFragment +import app.aaps.pump.equil.ui.EquilHistoryRecordActivity +import app.aaps.pump.equil.ui.EquilUnPairActivity +import app.aaps.pump.equil.ui.EquilUnPairDetachActivity +import app.aaps.pump.equil.ui.dlg.EquilAutoDressingDlg +import app.aaps.pump.equil.ui.dlg.EquilChangeInsulinDlg +import app.aaps.pump.equil.ui.dlg.EquilPairConfigDlg +import app.aaps.pump.equil.ui.dlg.EquilUnPairDlg +import app.aaps.pump.equil.ui.dlg.LoadingDlg +import app.aaps.pump.equil.ui.pair.EquilChangeInsulinFragment +import app.aaps.pump.equil.ui.pair.EquilPairActivity +import app.aaps.pump.equil.ui.pair.EquilPairAirFragment +import app.aaps.pump.equil.ui.pair.EquilPairAssembleFragment +import app.aaps.pump.equil.ui.pair.EquilPairAttachFragment +import app.aaps.pump.equil.ui.pair.EquilPairConfirmFragment +import app.aaps.pump.equil.ui.pair.EquilPairFillFragment +import app.aaps.pump.equil.ui.pair.EquilPairFragmentBase +import app.aaps.pump.equil.ui.pair.EquilPairSerialNumberFragment +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +@Suppress("unused") +abstract class EquilActivitiesModule { + + @ContributesAndroidInjector abstract fun contributesEquilFragment(): EquilFragment + @ContributesAndroidInjector abstract fun contributesEquilChangeInsulinDlg(): EquilChangeInsulinDlg + @ContributesAndroidInjector abstract fun contributesLoadingDlg(): LoadingDlg + @ContributesAndroidInjector abstract fun contributesEquilUnPairDlg(): EquilUnPairDlg + @ContributesAndroidInjector abstract fun contributesEquilPairConfigDlg(): EquilPairConfigDlg + @ContributesAndroidInjector abstract fun contributesEquilAutoDressingDlg(): EquilAutoDressingDlg + + @ContributesAndroidInjector abstract fun contributesEquilUnPairDetachActivity(): EquilUnPairDetachActivity + @ContributesAndroidInjector abstract fun contributesEquilUnPairActivity(): EquilUnPairActivity + @ContributesAndroidInjector abstract fun contributesEquilPairActivity(): EquilPairActivity + @ContributesAndroidInjector abstract fun contributesEquilPairFragment(): EquilPairFragmentBase + @ContributesAndroidInjector abstract fun contributesEquilPairAssembleFragment(): EquilPairAssembleFragment + @ContributesAndroidInjector abstract fun contributesEquilPairSerialNumberFragment(): EquilPairSerialNumberFragment + @ContributesAndroidInjector abstract fun contributesEquilPairFillFragment(): EquilPairFillFragment + @ContributesAndroidInjector abstract fun contributesEquilPairConfirmFragment(): EquilPairConfirmFragment + @ContributesAndroidInjector abstract fun contributesEquilPairAttachFragment(): EquilPairAttachFragment + @ContributesAndroidInjector abstract fun contributesEquilPairAirFragment(): EquilPairAirFragment + @ContributesAndroidInjector abstract fun contributesEquilChangeInsulinFragment(): EquilChangeInsulinFragment + + @ContributesAndroidInjector abstract fun contributesEquilHistoryRecordActivity(): EquilHistoryRecordActivity +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilHistoryModule.kt b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilHistoryModule.kt new file mode 100644 index 00000000000..66bf63099bd --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilHistoryModule.kt @@ -0,0 +1,28 @@ +package app.aaps.pump.equil.di + +import android.content.Context +import app.aaps.pump.equil.database.EquilHistoryDatabase +import app.aaps.pump.equil.database.EquilHistoryPumpDao +import app.aaps.pump.equil.database.EquilHistoryRecordDao +import dagger.Module +import dagger.Provides +import javax.inject.Singleton + +@Module +@Suppress("unused") +class EquilHistoryModule { + + @Provides + @Singleton + internal fun provideDatabase(context: Context): EquilHistoryDatabase = EquilHistoryDatabase.build(context) + + @Provides + @Singleton + internal fun provideHistoryRecordDao(equilHistoryDatabase: EquilHistoryDatabase): EquilHistoryRecordDao = + equilHistoryDatabase.historyRecordDao() + + @Provides + @Singleton + internal fun provideHistoryPumpDao(equilHistoryDatabase: EquilHistoryDatabase): EquilHistoryPumpDao = + equilHistoryDatabase.historyPumpDao() +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilModule.kt b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilModule.kt new file mode 100644 index 00000000000..c80fcd7f7de --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilModule.kt @@ -0,0 +1,13 @@ +package app.aaps.pump.equil.di + +import dagger.Module + +@Module( + includes = [ + EquilActivitiesModule::class, + EquilServicesModule::class, + EquilHistoryModule::class + ] +) + +open class EquilModule \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilServicesModule.kt b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilServicesModule.kt new file mode 100644 index 00000000000..02353d5ad96 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/di/EquilServicesModule.kt @@ -0,0 +1,12 @@ +package app.aaps.pump.equil.di + +import app.aaps.pump.equil.manager.EquilManager +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +@Suppress("unused") +abstract class EquilServicesModule { + + @ContributesAndroidInjector abstract fun contributesEquilManager(): EquilManager +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/ActivationProgress.kt b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/ActivationProgress.kt new file mode 100644 index 00000000000..711ded5380b --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/ActivationProgress.kt @@ -0,0 +1,21 @@ +package app.aaps.pump.equil.driver.definition + +enum class ActivationProgress { + NONE, + PRIMING, + CANNULA_CHANGE, + CANNULA_INSERTED, + COMPLETED; + + open fun isBefore(other: ActivationProgress): Boolean { + return ordinal < other.ordinal + } + + open fun isAtLeast(other: ActivationProgress): Boolean { + return ordinal >= other.ordinal + } + + open fun isAfter(other: ActivationProgress): Boolean { + return ordinal > other.ordinal + } +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalSchedule.kt b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalSchedule.kt new file mode 100644 index 00000000000..fdd9d6ddae0 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalSchedule.kt @@ -0,0 +1,52 @@ +package app.aaps.pump.equil.driver.definition + +import app.aaps.core.interfaces.profile.Profile +import org.joda.time.Duration +import java.util.Objects + +class BasalSchedule(private val entries: List) { + + init { + require(entries.isNotEmpty()) { "Entries can not be empty" } + require(entries[0].startTime.isEqual(Duration.ZERO)) { "First basal schedule entry should have 0 offset" } + } + + fun rateAt(offset: Duration): Double = lookup(offset).basalScheduleEntry.rate + fun getEntries(): List = ArrayList(entries) + + private fun lookup(offset: Duration): BasalScheduleLookupResult { + require(!(offset.isLongerThan(Duration.standardHours(24)) || offset.isShorterThan(Duration.ZERO))) { "Invalid duration" } + val reversedBasalScheduleEntries = reversedBasalScheduleEntries() + var last = Duration.standardHours(24) + for ((index, entry) in reversedBasalScheduleEntries.withIndex()) { + if (entry.startTime.isShorterThan(offset) || entry.startTime == offset) + return BasalScheduleLookupResult(reversedBasalScheduleEntries.size - (index + 1), entry, entry.startTime, last.minus(entry.startTime)) + last = entry.startTime + } + throw IllegalArgumentException("Basal schedule incomplete") + } + + private fun reversedBasalScheduleEntries(): List = ArrayList(entries).toMutableList().also { it.reverse() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + val that = other as BasalSchedule + return entries == that.entries + } + + override fun hashCode(): Int = Objects.hash(entries) + class BasalScheduleLookupResult internal constructor(val index: Int, val basalScheduleEntry: BasalScheduleEntry, val startTime: Duration, val duration: Duration) + companion object { + + fun mapProfileToBasalSchedule(profile: Profile): BasalSchedule { + val entries = ArrayList() + for (i in 0..23) { + val value = profile.getBasalTimeFromMidnight(i * 60 * 60) + val basalScheduleEntry = BasalScheduleEntry(value, Duration.standardSeconds((i * 60 * 60L))) + entries.add(basalScheduleEntry) + } + return BasalSchedule(entries) + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalScheduleEntry.kt b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalScheduleEntry.kt new file mode 100644 index 00000000000..658bdc685e8 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BasalScheduleEntry.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.driver.definition + +import org.joda.time.Duration + +data class BasalScheduleEntry(val rate: Double, val startTime: Duration) \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BluetoothConnectionState.kt b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BluetoothConnectionState.kt new file mode 100644 index 00000000000..506c90d0e54 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/BluetoothConnectionState.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.driver.definition + +enum class BluetoothConnectionState { + CONNECTING, CONNECTED, DISCONNECTED +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/EquilHistoryEntryGroup.kt b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/EquilHistoryEntryGroup.kt new file mode 100644 index 00000000000..20ff63dcc12 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/driver/definition/EquilHistoryEntryGroup.kt @@ -0,0 +1,37 @@ +package app.aaps.pump.equil.driver.definition + +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.pump.equil.R + +enum class EquilHistoryEntryGroup(val resourceId: Int) { + + All(R.string.equil_history_group_all), + Pair(R.string.equil_history_group_pair), + Bolus(R.string.equil_history_group_bolus), + Basal(R.string.equil_history_group_basal), + Configuration(R.string.equil_history_group_configuration), + ; + + var translated: String? = null + private set + + override fun toString(): String { + return translated!! + } + + companion object { + + private var translatedList: MutableList = mutableListOf() + + fun getTranslatedList(rh: ResourceHelper): List { + if (translatedList.isEmpty()) { + for (pumpHistoryEntryGroup in entries) { + pumpHistoryEntryGroup.translated = rh.gs(pumpHistoryEntryGroup.resourceId) + translatedList.add(pumpHistoryEntryGroup) + } + } + return translatedList + } + } +} + diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilDataChanged.kt b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilDataChanged.kt new file mode 100644 index 00000000000..d078f40a5a6 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilDataChanged.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.events + +import app.aaps.core.interfaces.rx.events.Event + +class EventEquilDataChanged : Event() diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilInsulinChanged.kt b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilInsulinChanged.kt new file mode 100644 index 00000000000..988c3283cc4 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilInsulinChanged.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.events + +import app.aaps.core.interfaces.rx.events.Event + +class EventEquilInsulinChanged : Event() diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilModeChanged.kt b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilModeChanged.kt new file mode 100644 index 00000000000..3ff6d592d4f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilModeChanged.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.events + +import app.aaps.core.interfaces.rx.events.Event + +class EventEquilModeChanged : Event() diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilUnPairChanged.kt b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilUnPairChanged.kt new file mode 100644 index 00000000000..28b6d132381 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/events/EventEquilUnPairChanged.kt @@ -0,0 +1,5 @@ +package app.aaps.pump.equil.events + +import app.aaps.core.interfaces.rx.events.Event + +class EventEquilUnPairChanged : Event() diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/AESUtil.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/AESUtil.java new file mode 100644 index 00000000000..7bcddeabf0f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/AESUtil.java @@ -0,0 +1,87 @@ +package app.aaps.pump.equil.manager; + + +import android.util.Log; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import app.aaps.core.interfaces.logging.LTag; + + +public class AESUtil { + private static byte[] generateAESKeyFromPassword(String password) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] inputBytes = password.getBytes(StandardCharsets.UTF_8); + byte[] hashBytes = digest.digest(inputBytes); + byte[] extractedBytes = new byte[16]; + System.arraycopy(hashBytes, 2, extractedBytes, 0, 16); + return extractedBytes; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static byte[] getEquilPassWord(String password) { + String plaintextDefault = "Equil"; // + byte[] defaultKey = generateAESKeyFromPassword(plaintextDefault); + byte[] aesKey = Utils.concat(defaultKey, generateAESKeyFromPassword(password)); + Log.e(LTag.PUMPCOMM.toString(), Utils.bytesToHex(aesKey) + "===" + aesKey.length); + return aesKey; + } + + public static byte[] generateRandomIV(int length) { + try { + SecureRandom secureRandom = new SecureRandom(); + byte[] ivBytes = new byte[length]; + secureRandom.nextBytes(ivBytes); + return ivBytes; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static EquilCmdModel aesEncrypt(byte[] pwd, byte[] data) throws Exception { + byte[] iv = generateRandomIV(12); + SecretKey key = new SecretKeySpec(pwd, "AES"); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec); + byte[] ciphertext = cipher.doFinal(data); + byte[] authenticationTag = Arrays.copyOfRange(ciphertext, ciphertext.length - 16, ciphertext.length); + byte[] encryptedData = Arrays.copyOfRange(ciphertext, 0, ciphertext.length - 16); + EquilCmdModel equilcmdmodel = new EquilCmdModel(); + equilcmdmodel.setTag(Utils.bytesToHex(authenticationTag)); + equilcmdmodel.setIv(Utils.bytesToHex(iv)); + equilcmdmodel.setCiphertext(Utils.bytesToHex(encryptedData)); + return equilcmdmodel; + } + + + public static String decrypt(EquilCmdModel equilCmdModel, byte[] keyBytes) throws Exception { + String iv = equilCmdModel.getIv(); + String ciphertext = equilCmdModel.getCiphertext(); + String authenticationTag = equilCmdModel.getTag(); + byte[] ivBytes = Utils.hexStringToBytes(iv); + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + byte[] decodedCiphertext = Utils.hexStringToBytes(ciphertext); + byte[] decodedAuthenticationTag = Utils.hexStringToBytes(authenticationTag); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec parameterSpec = new GCMParameterSpec(128, ivBytes); + cipher.init(Cipher.DECRYPT_MODE, keySpec, parameterSpec); + cipher.update(decodedCiphertext); + byte[] decrypted = cipher.doFinal(decodedAuthenticationTag); + String content = Utils.bytesToHex(decrypted); + return content; + } +} \ No newline at end of file diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/Crc.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/Crc.java new file mode 100644 index 00000000000..c1b5a5d12a0 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/Crc.java @@ -0,0 +1,49 @@ +package app.aaps.pump.equil.manager; + +public class Crc { + + public static int CRC8_MAXIM(byte[] source) { + int offset = 0; + int length = source.length; + int wCRCin = 0x00; + // Integer.reverse(0x31) >>> 24 + int wCPoly = 0x8C; + for (int i = offset, cnt = offset + length; i < cnt; i++) { + wCRCin ^= ((long) source[i] & 0xFF); + for (int j = 0; j < 8; j++) { + if ((wCRCin & 0x01) != 0) { + wCRCin >>= 1; + wCRCin ^= wCPoly; + } else { + wCRCin >>= 1; + } + } + } + return wCRCin ^= 0x00; + } + + + public static byte[] getCRC(byte[] bytes) { + int CRC = 0x0000ffff; + int POLYNOMIAL = 0x0000a001; + int i, j; + for (i = 0; i < bytes.length; i++) { + CRC ^= ((int) bytes[i] & 0x000000ff); + for (j = 0; j < 8; j++) { + if ((CRC & 0x00000001) != 0) { + CRC >>= 1; + CRC ^= POLYNOMIAL; + } else { + CRC >>= 1; + } + } + } + String result = Integer.toHexString(CRC).toUpperCase(); + if (result.length() != 4) { + StringBuffer sb = new StringBuffer("0000"); + result = sb.replace(4 - result.length(), 4, result).toString(); + } + return Utils.hexStringToBytes(result); + } + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilCmdModel.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilCmdModel.java new file mode 100644 index 00000000000..6e3f332f7b7 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilCmdModel.java @@ -0,0 +1,51 @@ +package app.aaps.pump.equil.manager; + +import androidx.annotation.NonNull; + +public class EquilCmdModel { + private String code; + private String iv; + private String tag; + private String ciphertext; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getIv() { + return iv; + } + + public void setIv(String iv) { + this.iv = iv; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + @NonNull @Override public String toString() { + return "EquilCmdModel{" + + "code='" + code + '\'' + + ", iv='" + iv + '\'' + + ", tag='" + tag + '\'' + + ", ciphertext='" + ciphertext + '\'' + + '}'; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilManager.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilManager.java new file mode 100644 index 00000000000..a356ce8e3e6 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilManager.java @@ -0,0 +1,1157 @@ +package app.aaps.pump.equil.manager; + +import android.os.SystemClock; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializer; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.ISODateTimeFormat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +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.objects.Instantiator; +import app.aaps.core.interfaces.profile.Profile; +import app.aaps.core.interfaces.pump.DetailedBolusInfo; +import app.aaps.core.interfaces.pump.PumpEnactResult; +import app.aaps.core.interfaces.pump.PumpSync; +import app.aaps.core.interfaces.pump.defs.PumpType; +import app.aaps.core.interfaces.resources.ResourceHelper; +import app.aaps.core.interfaces.rx.bus.RxBus; +import app.aaps.core.interfaces.rx.events.Event; +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.utils.HardLimits; +import app.aaps.core.main.events.EventNewNotification; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.R; +import app.aaps.pump.equil.ble.EquilBLE; +import app.aaps.pump.equil.data.AlarmMode; +import app.aaps.pump.equil.data.BolusProfile; +import app.aaps.pump.equil.data.RunMode; +import app.aaps.pump.equil.database.BolusType; +import app.aaps.pump.equil.database.EquilBasalValuesRecord; +import app.aaps.pump.equil.database.EquilBolusRecord; +import app.aaps.pump.equil.database.EquilHistoryPump; +import app.aaps.pump.equil.database.EquilHistoryPumpDao; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.database.EquilHistoryRecordDao; +import app.aaps.pump.equil.database.EquilTempBasalRecord; +import app.aaps.pump.equil.database.ResolvedResult; +import app.aaps.pump.equil.driver.definition.ActivationProgress; +import app.aaps.pump.equil.driver.definition.BasalSchedule; +import app.aaps.pump.equil.driver.definition.BluetoothConnectionState; +import app.aaps.pump.equil.events.EventEquilDataChanged; +import app.aaps.pump.equil.events.EventEquilInsulinChanged; +import app.aaps.pump.equil.events.EventEquilModeChanged; +import app.aaps.pump.equil.manager.command.BaseCmd; +import app.aaps.pump.equil.manager.command.CmdBasalGet; +import app.aaps.pump.equil.manager.command.CmdBasalSet; +import app.aaps.pump.equil.manager.command.CmdExtendedBolusSet; +import app.aaps.pump.equil.manager.command.CmdHistoryGet; +import app.aaps.pump.equil.manager.command.CmdLargeBasalSet; +import app.aaps.pump.equil.manager.command.CmdModelGet; +import app.aaps.pump.equil.manager.command.CmdTempBasalGet; +import app.aaps.pump.equil.manager.command.CmdTempBasalSet; +import app.aaps.pump.equil.manager.command.PumpEvent; +import dagger.android.HasAndroidInjector; + +@Singleton +public class EquilManager { + private final HasAndroidInjector injector; + private final AAPSLogger aapsLogger; + private final RxBus rxBus; + private final ResourceHelper rh; + private final SP sp; + private final PumpSync pumpSync; + private final Instantiator instantiator; + EquilBLE equilBLE; + EquilHistoryRecordDao equilHistoryRecordDao; + EquilHistoryPumpDao equilHistoryPumpDao; + + public AAPSLogger getAapsLogger() { + return aapsLogger; + } + + public SP getSp() { + return sp; + } + + + @Inject + public EquilManager( + HasAndroidInjector injector, + AAPSLogger aapsLogger, + RxBus rxBus, + SP sp, + ResourceHelper rh, + PumpSync pumpSync, + EquilBLE equilBLE, + EquilHistoryRecordDao equilHistoryRecordDao, + EquilHistoryPumpDao equilHistoryPumpDao, + Instantiator instantiator + ) { + this.injector = injector; + this.aapsLogger = aapsLogger; + this.rxBus = rxBus; + this.sp = sp; + this.rh = rh; + this.pumpSync = pumpSync; + this.equilBLE = equilBLE; + this.equilHistoryRecordDao = equilHistoryRecordDao; + this.equilHistoryPumpDao = equilHistoryPumpDao; + this.instantiator = instantiator; + + this.gsonInstance = createGson(); + loadPodState(); + initEquilError(); + equilBLE.init(this); + equilBLE.getEquilStatus(); + } + + List listEvent; + + private void initEquilError() { + listEvent = new ArrayList<>(); + listEvent.add(new PumpEvent(4, 2, 2, rh.gs(R.string.equil_history_item3))); + listEvent.add(new PumpEvent(4, 3, 0, rh.gs(R.string.equil_history_item4))); + listEvent.add(new PumpEvent(4, 3, 2, rh.gs(R.string.equil_history_item5))); + listEvent.add(new PumpEvent(4, 6, 1, rh.gs(R.string.equil_shutdown_be))); + listEvent.add(new PumpEvent(4, 6, 2, rh.gs(R.string.equil_shutdown))); + listEvent.add(new PumpEvent(4, 8, 0, rh.gs(R.string.equil_shutdown))); + listEvent.add(new PumpEvent(5, 1, 2, rh.gs(R.string.equil_history_item18))); + + } + + public String getEquilError(int port, int type, int level) { + PumpEvent pumpEvent = new PumpEvent(port, type, level, ""); + int index = listEvent.indexOf(pumpEvent); + if (index == -1) { + return ""; + } + return listEvent.get(index).getConent(); + } + + public void closeBleAuto() { + equilBLE.closeBleAuto(); + + } + + public PumpEnactResult closeBle() { + PumpEnactResult result = new PumpEnactResult(injector); + try { + equilBLE.disconnect(); + } catch (Exception ex) { + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult readStatus() { + PumpEnactResult result = new PumpEnactResult(injector); + try { + equilBLE.getEquilStatus(); + } catch (Exception ex) { + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult getTempBasalPump() { + PumpEnactResult result = new PumpEnactResult(injector); + try { + CmdTempBasalGet command = new CmdTempBasalGet(); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + result.setSuccess(command.isCmdStatus()); + result.enacted(command.getTime() != 0); + SystemClock.sleep(EquilConst.EQUIL_BLE_NEXT_CMD); + } catch (Exception ex) { + ex.printStackTrace(); + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult setTempBasal(double insulin, int time, boolean cancel) { + PumpEnactResult result = new PumpEnactResult(injector); + try { + CmdTempBasalSet command = new CmdTempBasalSet(insulin, time); + command.setCancel(cancel); + EquilHistoryRecord equilHistoryRecord = addHistory(command); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + if (command.isCmdStatus()) { + long currentTime = System.currentTimeMillis(); + if (cancel) { + pumpSync.syncStopTemporaryBasalWithPumpId( + currentTime, + currentTime, + PumpType.EQUIL, + getSerialNumber() + ); + setTempBasal(null); + } else { + EquilTempBasalRecord tempBasalRecord = + new EquilTempBasalRecord(time * 60 * 1000, + insulin, currentTime); + setTempBasal(tempBasalRecord); + pumpSync.syncTemporaryBasalWithPumpId( + currentTime, + insulin, + (long) time * 60 * 1000, + true, + PumpSync.TemporaryBasalType.NORMAL, + currentTime, + PumpType.EQUIL, + getSerialNumber() + ); + } + command.setResolvedResult(ResolvedResult.SUCCESS); + } + updateHistory(equilHistoryRecord, command.getResolvedResult()); + result.setSuccess(command.isCmdStatus()); + result.enacted(true); + } catch (Exception ex) { + ex.printStackTrace(); + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult setExtendedBolus(double insulin, int time, boolean cancel) { + PumpEnactResult result = new PumpEnactResult(injector); + try { + CmdExtendedBolusSet command = new CmdExtendedBolusSet(insulin, time, cancel); + EquilHistoryRecord equilHistoryRecord = addHistory(command); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + + result.setSuccess(command.isCmdStatus()); + if (command.isCmdStatus()) { + command.setResolvedResult(ResolvedResult.SUCCESS); + long currentTimeMillis = System.currentTimeMillis(); + if (cancel) { + pumpSync.syncStopExtendedBolusWithPumpId( + currentTimeMillis, + currentTimeMillis, + PumpType.EQUIL, + getSerialNumber() + ); + } else { + pumpSync.syncExtendedBolusWithPumpId( + currentTimeMillis, + insulin, + (long) time * 60 * 1000, + true, + currentTimeMillis, + PumpType.EQUIL, + getSerialNumber() + ); + } + + result.enacted(true); + } else { + result.setSuccess(false); + } + updateHistory(equilHistoryRecord, command.getResolvedResult()); + } catch (Exception ex) { + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + + public PumpEnactResult bolus(DetailedBolusInfo detailedBolusInfo, BolusProfile bolusProfile) { + EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE; + progressUpdateEvent.setT(new EventOverviewBolusProgress.Treatment(HardLimits.MAX_IOB_LGS, 0, + detailedBolusInfo.getBolusType() == + DetailedBolusInfo.BolusType.SMB, detailedBolusInfo.getId())); + PumpEnactResult result = new PumpEnactResult(injector); + try { + CmdLargeBasalSet command = new CmdLargeBasalSet(detailedBolusInfo.insulin); + EquilHistoryRecord equilHistoryRecord = addHistory(command); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + bolusProfile.setStop(false); + int sleep = command.getStepTime() / 20 * 200; + sleep = 2000; + float percent1 = (float) (5f / detailedBolusInfo.insulin); + aapsLogger.debug(LTag.PUMPCOMM, "sleep===" + detailedBolusInfo.insulin + "===" + percent1); + float percent = 0; + if (command.isCmdStatus()) { + result.setSuccess(true); + result.enacted(true); + while (!bolusProfile.getStop() && percent < 100) { + progressUpdateEvent.setPercent((int) percent); + progressUpdateEvent.setStatus(this.rh.gs(R.string.equil_bolus_delivered, + percent / 100d * detailedBolusInfo.insulin, + detailedBolusInfo.insulin)); + rxBus.send(progressUpdateEvent); + SystemClock.sleep(sleep); + percent = percent + percent1; + aapsLogger.debug(LTag.PUMPCOMM, "isCmdStatus===" + percent + "====" + bolusProfile.getStop()); + } + result.setComment(rh.gs(app.aaps.core.ui.R.string.virtualpump_resultok)); + } else { + result.setSuccess(false); + result.enacted(false); + result.setComment(rh.gs(R.string.equil_command_connect_error)); + } + result.setBolusDelivered(percent / 100d * detailedBolusInfo.insulin); + if (result.getSuccess()) { + command.setResolvedResult(ResolvedResult.SUCCESS); + long currentTime = System.currentTimeMillis(); + pumpSync.syncBolusWithPumpId(currentTime, + result.getBolusDelivered(), + detailedBolusInfo.getBolusType(), + detailedBolusInfo.timestamp, + PumpType.EQUIL, + getSerialNumber()); + EquilBolusRecord equilBolusRecord = + new EquilBolusRecord(result.getBolusDelivered(), + BolusType.SMB, currentTime); + setBolusRecord(equilBolusRecord); + + } + updateHistory(equilHistoryRecord, command.getResolvedResult()); + } catch (Exception ex) { + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult stopBolus(BolusProfile bolusProfile) { + PumpEnactResult result = new PumpEnactResult(injector); + try { + BaseCmd command = new CmdLargeBasalSet(0); + EquilHistoryRecord equilHistoryRecord = addHistory(command); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + bolusProfile.setStop(command.isCmdStatus()); + aapsLogger.debug(LTag.PUMPCOMM, "stopBolus==="); + result.setSuccess(command.isCmdStatus()); + if (command.isCmdStatus()) { + command.setResolvedResult(ResolvedResult.SUCCESS); + } + updateHistory(equilHistoryRecord, command.getResolvedResult()); + result.enacted(true); + } catch (Exception ex) { + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public int loadEquilHistory(int index) { + try { + aapsLogger.debug(LTag.PUMPCOMM, "loadHistory start: "); + CmdHistoryGet historyGet = new CmdHistoryGet(index); + historyGet.setEquilManager(this); + equilBLE.readHistory(historyGet); + synchronized (historyGet) { + historyGet.wait(historyGet.getTimeOut()); + } + aapsLogger.debug(LTag.PUMPCOMM, "loadHistory end: "); + return historyGet.getCurrentIndex(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return -1; + } + + public int loadHistory(int index) { + try { + CmdHistoryGet historyGet = new CmdHistoryGet(index); + historyGet.setEquilManager(this); + equilBLE.writeCmd(historyGet); + synchronized (historyGet) { + historyGet.wait(historyGet.getTimeOut()); + } + aapsLogger.debug(LTag.PUMPCOMM, "loadHistory end: "); + return historyGet.getCurrentIndex(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return -1; + } + + public PumpEnactResult getBasal(Profile profile) { + PumpEnactResult result = new PumpEnactResult(injector); + try { + CmdBasalGet cmdBasalGet = new CmdBasalGet(profile); + cmdBasalGet.setEquilManager(this); + equilBLE.writeCmd(cmdBasalGet); + synchronized (cmdBasalGet) { + cmdBasalGet.wait(cmdBasalGet.getTimeOut()); + } + result.setSuccess(cmdBasalGet.isCmdStatus()); + } catch (Exception ex) { + ex.printStackTrace(); + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public EquilHistoryRecord addHistory(BaseCmd command) { + EquilHistoryRecord equilHistoryRecord = new EquilHistoryRecord(System.currentTimeMillis(), getSerialNumber()); + if (command.getEventType() != null) { + equilHistoryRecord.setType(command.getEventType()); + } + if (command instanceof CmdBasalSet) { + Profile profile = ((CmdBasalSet) command).getProfile(); + equilHistoryRecord.setBasalValuesRecord(new EquilBasalValuesRecord(Arrays.asList(profile.getBasalValues()))); + } + if (command instanceof CmdTempBasalSet) { + CmdTempBasalSet cmd = ((CmdTempBasalSet) command); + boolean cancel = cmd.isCancel(); + if (!cancel) { + EquilTempBasalRecord equilTempBasalRecord = + new EquilTempBasalRecord(cmd.getDuration() * 60 * 1000, + cmd.getInsulin(), System.currentTimeMillis()); + equilHistoryRecord.setTempBasalRecord(equilTempBasalRecord); + } + } + if (command instanceof CmdExtendedBolusSet) { + CmdExtendedBolusSet cmd = ((CmdExtendedBolusSet) command); + boolean cancel = cmd.isCancel(); + if (!cancel) { + EquilTempBasalRecord equilTempBasalRecord = + new EquilTempBasalRecord(cmd.getDurationInMinutes() * 60 * 1000, + cmd.getInsulin(), System.currentTimeMillis()); + equilHistoryRecord.setTempBasalRecord(equilTempBasalRecord); + } + } + if (command instanceof CmdLargeBasalSet) { + CmdLargeBasalSet cmd = ((CmdLargeBasalSet) command); + double insulin = cmd.getInsulin(); + if (insulin != 0) { + EquilBolusRecord equilBolusRecord = + new EquilBolusRecord(insulin, + BolusType.SMB, System.currentTimeMillis()); + equilHistoryRecord.setBolusRecord(equilBolusRecord); + } + } + + if (equilHistoryRecord.getType() != null) { + long id = equilHistoryRecordDao.insert(equilHistoryRecord); + equilHistoryRecord.setId(id); + aapsLogger.debug(LTag.PUMPCOMM, "equilHistoryRecord is {}", id); + } + return equilHistoryRecord; + } + + public void updateHistory(EquilHistoryRecord equilHistoryRecord, ResolvedResult result) { + if (result != null && equilHistoryRecord != null) { + aapsLogger.debug(LTag.PUMPCOMM, "equilHistoryRecord2 is {} {}", + equilHistoryRecord.getId(), result); + equilHistoryRecord.setResolvedAt(System.currentTimeMillis()); + equilHistoryRecord.setResolvedStatus(result); + int status = equilHistoryRecordDao.update(equilHistoryRecord); + aapsLogger.debug(LTag.PUMPCOMM, "equilHistoryRecord3== is {} {} status {}", + equilHistoryRecord.getId(), equilHistoryRecord.getResolvedStatus(), status); + } + } + + public PumpEnactResult readEquilStatus() { + PumpEnactResult result = new PumpEnactResult(injector); + try { + BaseCmd command = new CmdModelGet(); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + if (command.isCmdStatus()) { + command.setResolvedResult(ResolvedResult.SUCCESS); + SystemClock.sleep(EquilConst.EQUIL_BLE_NEXT_CMD); + return loadEquilHistory(); + } + result.setSuccess(command.isCmdStatus()); + result.enacted(command.isEnacted()); + } catch (Exception ex) { + ex.printStackTrace(); + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + public PumpEnactResult loadEquilHistory() { + PumpEnactResult pumpEnactResult = new PumpEnactResult(injector); + int startIndex; + startIndex = getStartHistoryIndex(); + int index = getHistoryIndex(); + aapsLogger.debug(LTag.PUMPCOMM, "return ===" + index + "====" + startIndex); + if (index == -1) { + return pumpEnactResult.success(false); + } + int allCount = 1; + while (startIndex != index && allCount < 20) { + startIndex++; + if (startIndex > 2000) { + startIndex = 1; + } + SystemClock.sleep(EquilConst.EQUIL_BLE_NEXT_CMD); + int currentIndex = loadEquilHistory(startIndex); + aapsLogger.debug(LTag.PUMPCOMM, "while index===" + startIndex + "===" + index + "===" + currentIndex); + if (currentIndex > 1) { + setStartHistoryIndex(currentIndex); + allCount++; + } else { + break; + } + } + return pumpEnactResult.success(true); + } + + public PumpEnactResult executeCmd(BaseCmd command) { + PumpEnactResult result = new PumpEnactResult(injector); + try { + EquilHistoryRecord equilHistoryRecord = addHistory(command); + command.setEquilManager(this); + equilBLE.writeCmd(command); + synchronized (command) { + command.wait(command.getTimeOut()); + } + if (command.isCmdStatus()) { + command.setResolvedResult(ResolvedResult.SUCCESS); + } + updateHistory(equilHistoryRecord, command.getResolvedResult()); + aapsLogger.debug(LTag.PUMPCOMM, "executeCmd result {}", command.getResolvedResult()); + result.setSuccess(command.isCmdStatus()); + result.enacted(command.isEnacted()); + } catch (Exception ex) { + + ex.printStackTrace(); + result.success(false).enacted(false).comment(translateException(ex)); + } + return result; + } + + + public String translateException(Throwable ex) { + return ""; + } + + private void handleException(Exception ex) { + aapsLogger.error(LTag.PUMP, "Caught an unexpected non-OmnipodException from OmnipodManager", ex); + } + + public boolean isConnected() { + return equilBLE.isConnected(); + } + + public void showNotification(int id, String message, int urgency, Integer sound) { + Notification notification = new Notification( // + id, // + message, // + urgency); + if (sound != null) { + notification.setSoundId(sound); + } + sendEvent(new EventNewNotification(notification)); + } + + public void dismissNotification(int id) { + sendEvent(new EventDismissNotification(id)); + } + + private void sendEvent(Event event) { + rxBus.send(event); + } + + private final Gson gsonInstance; + private EquilState equilState; + + private static Gson createGson() { + GsonBuilder gsonBuilder = new GsonBuilder() + .registerTypeAdapter(DateTime.class, (JsonSerializer) (dateTime, typeOfSrc, context) -> + new JsonPrimitive(ISODateTimeFormat.dateTime().print(dateTime))) + .registerTypeAdapter(DateTime.class, (JsonDeserializer) (json, typeOfT, context) -> + ISODateTimeFormat.dateTime().parseDateTime(json.getAsString())) + .registerTypeAdapter(DateTimeZone.class, (JsonSerializer) (timeZone, typeOfSrc, context) -> + new JsonPrimitive(timeZone.getID())) + .registerTypeAdapter(DateTimeZone.class, (JsonDeserializer) (json, typeOfT, context) -> + DateTimeZone.forID(json.getAsString())); + + return gsonBuilder.create(); + } + + protected String readPodState() { + return sp.getString(EquilConst.Prefs.INSTANCE.getEQUIL_STATE(), ""); + } + + public final void loadPodState() { + equilState = null; + + String storedPodState = readPodState(); + + if (StringUtils.isEmpty(storedPodState)) { + equilState = new EquilState(); + aapsLogger.info(LTag.PUMP, "loadPodState: no Pod state was provided"); + } else { + aapsLogger.info(LTag.PUMP, "loadPodState: serialized Pod state was provided: " + storedPodState); + try { + equilState = gsonInstance.fromJson(storedPodState, EquilState.class); + } catch (Exception ex) { + equilState = new EquilState(); + aapsLogger.error(LTag.PUMP, "loadPodState: could not deserialize PodState: " + storedPodState, ex); + } + } + } + + public final boolean hasPodState() { + + return this.equilState != null; // 0x0=discarded + } + + private void setSafe(Runnable runnable) { + if (!hasPodState()) { + throw new IllegalStateException("Cannot mutate PodState: podState is null"); + } + runnable.run(); + } + + public void storePodState() { + String podState = gsonInstance.toJson(this.equilState); + aapsLogger.debug(LTag.PUMP, "storePodState: storing podState: {}", podState); + storePodState(podState); + } + + public void clearPodState() { + this.equilState = new EquilState(); + String podState = gsonInstance.toJson(equilState); + aapsLogger.debug(LTag.PUMP, "storePodState: storing podState: {}", podState); + storePodState(podState); + } + + private void setAndStore(Runnable runnable) { + setSafe(runnable); + storePodState(); + } + + private T getSafe(Supplier supplier) { + if (!hasPodState()) { + throw new IllegalStateException("Cannot read from PodState: podState is null"); + } + return supplier.get(); + } + + protected void storePodState(String podState) { + sp.putString(EquilConst.Prefs.INSTANCE.getEQUIL_STATE(), podState); + } + + public String getSerialNumber() { + return getSafe(() -> equilState.getSerialNumber()); + } + + public final void setSerialNumber(String serialNumber) { + setAndStore(() -> equilState.setSerialNumber(serialNumber)); + } + + public EquilBolusRecord getBolusRecord() { + return getSafe(() -> equilState.getBolusRecord()); + } + + public final void setBolusRecord(EquilBolusRecord bolusRecord) { + setAndStore(() -> equilState.setBolusRecord(bolusRecord)); + } + + + public EquilTempBasalRecord getTempBasal() { + return getSafe(() -> equilState.getTempBasal()); + } + + public final boolean hasTempBasal() { + return getTempBasal() != null; + } + + public final boolean isTempBasalRunning() { + return isTempBasalRunningAt(null); + } + + public final boolean isTempBasalRunningAt(DateTime time) { + if (time == null) { // now + if (!hasTempBasal()) { + return true; + } + time = DateTime.now(); + } + EquilTempBasalRecord equilTempBasalRecord = getTempBasal(); + if (hasTempBasal()) { + DateTime tempBasalStartTime = new DateTime(equilTempBasalRecord.getStartTime()); + DateTime tempBasalEndTime = tempBasalStartTime.plus(equilTempBasalRecord.getDuration()); + return (time.isAfter(tempBasalStartTime) || time.isEqual(tempBasalStartTime)) && time.isBefore(tempBasalEndTime); + } + return false; + } + + public final boolean isPumpRunning() { + return getRunMode() == RunMode.RUN; + } + + public final void setTempBasal(EquilTempBasalRecord tempBasal) { + setAndStore(() -> equilState.setTempBasal(tempBasal)); + } + + + public long getLastDataTime() { + return getSafe(() -> equilState.getLastDataTime()); + } + + public void setLastDataTime(long lastDataTime) { + setAndStore(() -> equilState.setLastDataTime(lastDataTime)); + } + + public long getDevicesTime() { + return getSafe(() -> equilState.getDevicesTime()); + } + + public void setDevicesTime(long devicesTime) { + setAndStore(() -> equilState.setDevicesTime(devicesTime)); + } + + public int getCurrentInsulin() { + return getSafe(() -> equilState.getCurrentInsulin()); + } + + public void setCurrentInsulin(int currentInsulin) { + setAndStore(() -> equilState.setCurrentInsulin(currentInsulin)); + + } + + public int getStartInsulin() { + return getSafe(() -> equilState.getStartInsulin()); +// return 200; + } + + public void setStartInsulin(int startInsulin) { + aapsLogger.debug(LTag.PUMPCOMM, "startInsulin {}", startInsulin); + setAndStore(() -> equilState.setStartInsulin(startInsulin)); + + } + + public int getBattery() { + return getSafe(() -> equilState.getBattery()); + + } + + public void setBattery(int battery) { + setAndStore(() -> equilState.setBattery(battery)); + + } + + public RunMode getRunMode() { + return getSafe(() -> equilState.getRunMode()); + } + + public void setRunMode(RunMode runMode) { + setAndStore(() -> equilState.setRunMode(runMode)); + } + + public String getFirmwareVersion() { + return getSafe(() -> equilState.getFirmwareVersion()); + + } + + public void setFirmwareVersion(String firmwareVersion) { + setAndStore(() -> equilState.setFirmwareVersion(firmwareVersion)); + } + + public float getRate() { + return getSafe(() -> equilState.getRate()); + } + + public void setRate(float rate) { + setAndStore(() -> equilState.setRate(rate)); + } + + public int getHistoryIndex() { + return getSafe(() -> equilState.getHistoryIndex()); + + } + + public void setHistoryIndex(int historyIndex) { + setAndStore(() -> equilState.setHistoryIndex(historyIndex)); + + } + + public String getAddress() { + return getSafe(() -> equilState.getAddress()); + } + + public void setAddress(String address) { + setAndStore(() -> equilState.setAddress(address)); + + } + + public final ActivationProgress getActivationProgress() { + if (hasPodState()) { + return Optional.ofNullable(equilState.getActivationProgress()).orElse(ActivationProgress.NONE); + } + return ActivationProgress.NONE; + } + + public final boolean isActivationCompleted() { + return getActivationProgress() == ActivationProgress.COMPLETED; + } + + public final boolean isActivationInitialized() { + return getActivationProgress() != ActivationProgress.NONE; + } + + public void setActivationProgress(ActivationProgress activationProgress) { + setAndStore(() -> equilState.setActivationProgress(activationProgress)); + } + + + public BluetoothConnectionState getBluetoothConnectionState() { + return getSafe(() -> equilState.getBluetoothConnectionState()); + } + + public void setBluetoothConnectionState(BluetoothConnectionState bluetoothConnectionState) { + setAndStore(() -> equilState.setBluetoothConnectionState(bluetoothConnectionState)); + } + + public int getStartHistoryIndex() { + return getSafe(() -> equilState.getStartHistoryIndex()); + + } + + public void setStartHistoryIndex(int startHistoryIndex) { + setAndStore(() -> equilState.setStartHistoryIndex(startHistoryIndex)); + + } + + public BasalSchedule getBasalSchedule() { + return getSafe(() -> equilState.getBasalSchedule()); + + } + + public void setBasalSchedule(BasalSchedule basalSchedule) { + setAndStore(() -> equilState.setBasalSchedule(basalSchedule)); + + } + + private static final class EquilState { + private ActivationProgress activationProgress; + private String serialNumber; + private String address; + private String firmwareVersion; + + private long lastDataTime; + private long devicesTime; + private int currentInsulin; + private int startInsulin; + private int battery; + private EquilTempBasalRecord tempBasal; + private EquilBolusRecord bolusRecord; + private RunMode runMode; + @NonNull private AlarmMode alarmMode = AlarmMode.TONE_AND_SHAKE; + private float rate; + private int historyIndex; + + BluetoothConnectionState bluetoothConnectionState = BluetoothConnectionState.DISCONNECTED; + private int startHistoryIndex; + private BasalSchedule basalSchedule; + + public BasalSchedule getBasalSchedule() { + return basalSchedule; + } + + public void setBasalSchedule(BasalSchedule basalSchedule) { + this.basalSchedule = basalSchedule; + } + + public int getStartHistoryIndex() { + return startHistoryIndex; + } + + public void setStartHistoryIndex(int startHistoryIndex) { + this.startHistoryIndex = startHistoryIndex; + } + + public BluetoothConnectionState getBluetoothConnectionState() { + return bluetoothConnectionState; + } + + public void setBluetoothConnectionState(BluetoothConnectionState bluetoothConnectionState) { + this.bluetoothConnectionState = bluetoothConnectionState; + } + + public ActivationProgress getActivationProgress() { + return activationProgress; + } + + public void setActivationProgress(ActivationProgress activationProgress) { + this.activationProgress = activationProgress; + } + + public int getHistoryIndex() { + return historyIndex; + } + + public void setHistoryIndex(int historyIndex) { + this.historyIndex = historyIndex; + } + + public float getRate() { + return rate; + } + + public void setRate(float rate) { + this.rate = rate; + } + + @NonNull public AlarmMode getAlarmMode() { + return alarmMode; + } + + public void setAlarmMode(AlarmMode alarmMode) { + this.alarmMode = alarmMode; + } + + public RunMode getRunMode() { + return runMode; + } + + public void setRunMode(RunMode runMode) { + this.runMode = runMode; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public long getLastDataTime() { + return lastDataTime; + } + + public void setLastDataTime(long lastDataTime) { + this.lastDataTime = lastDataTime; + } + + public long getDevicesTime() { + return devicesTime; + } + + public void setDevicesTime(long devicesTime) { + this.devicesTime = devicesTime; + } + + public int getCurrentInsulin() { + return currentInsulin; + } + + public void setCurrentInsulin(int currentInsulin) { + this.currentInsulin = currentInsulin; + } + + public int getStartInsulin() { + return startInsulin; + } + + public void setStartInsulin(int startInsulin) { + this.startInsulin = startInsulin; + } + + public String getFirmwareVersion() { + return firmwareVersion; + } + + public void setFirmwareVersion(String firmwareVersion) { + this.firmwareVersion = firmwareVersion; + } + + + public int getBattery() { + return battery; + } + + public void setBattery(int battery) { + this.battery = battery; + } + + public EquilTempBasalRecord getTempBasal() { + return tempBasal; + } + + public void setTempBasal(EquilTempBasalRecord tempBasal) { + this.tempBasal = tempBasal; + } + + public EquilBolusRecord getBolusRecord() { + return bolusRecord; + } + + public void setBolusRecord(EquilBolusRecord bolusRecord) { + this.bolusRecord = bolusRecord; + } + } + + public void setModel(int modeint) { + if (modeint == 0) { + setRunMode(RunMode.SUSPEND); + } else if (modeint == 1) { + setRunMode(RunMode.RUN); + } else if (modeint == 2) { + setRunMode(RunMode.STOP); + + } else { + setRunMode(RunMode.SUSPEND); + } + rxBus.send(new EventEquilModeChanged()); + } + + public void setInsulinChange(int status) { + if (status == 1) { + rxBus.send(new EventEquilInsulinChanged()); + } + } + + public void decodeHistory(byte[] data) { + int year = data[6] & 0xff; + year = year + 2000; + + int month = data[7] & 0xff; + int day = data[8] & 0xff; + int hour = data[9] & 0xff; + int min = data[10] & 0xff; + int second = data[11] & 0xff; + //a5e207590501 17070e100f161100000000007d0204080000 + //ae6ae9100501 17070e100f16 1100000000007d0204080000 + int battery = data[12] & 0xff; + int insulin = data[13] & 0xff; + int rate = Utils.bytesToInt(data[15], data[14]); + int largeRate = Utils.bytesToInt(data[17], data[16]); + int index = Utils.bytesToInt(data[19], data[18]); + + int port = data[20] & 0xff; + int type = data[21] & 0xff; + int level = data[22] & 0xff; + int parm = data[23] & 0xff; + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, second); + calendar.set(Calendar.MILLISECOND, 0); + EquilHistoryPump equilHistoryPump = new EquilHistoryPump(); + equilHistoryPump.setBattery(battery); + equilHistoryPump.setInsulin(insulin); + equilHistoryPump.setRate(rate); + equilHistoryPump.setLargeRate(largeRate); + equilHistoryPump.setTimestamp(System.currentTimeMillis()); + equilHistoryPump.setEventTimestamp((calendar.getTimeInMillis() + index)); + equilHistoryPump.setPort(port); + equilHistoryPump.setType(type); + equilHistoryPump.setLevel(level); + equilHistoryPump.setParm(parm); + equilHistoryPump.setEventIndex(index); + equilHistoryPump.setSerialNumber(getSerialNumber()); + long id = equilHistoryPumpDao.insert(equilHistoryPump); + aapsLogger.debug(LTag.PUMPCOMM, "decodeHistory insert id {}", id); + rxBus.send(new EventEquilDataChanged()); + } + + public void decodeData(byte[] data) { + int year = data[11] & 0xFF; + year = year + 2000; + int month = data[12] & 0xff; + int day = data[13] & 0xff; + int hour = data[14] & 0xff; + int min = data[15] & 0xff; + int second = data[16] & 0xff; + int battery = data[17] & 0xff; + int insulin = data[18] & 0xff; + int rate1 = Utils.bytesToInt(data[20], data[19]); + float rate = Utils.internalDecodeSpeedToUH(rate1); + float largeRate = Utils.bytesToInt(data[22], data[21]); + int historyIndex = Utils.bytesToInt(data[24], data[23]); + int currentIndex = getHistoryIndex(); + int port = data[25] & 0xff; + int level = data[26] & 0xff; + int parm = data[27] & 0xff; + String errorTips = getEquilError(port, level, parm); + if (!TextUtils.isEmpty(errorTips) && currentIndex != historyIndex) { + showNotification(Notification.FAILED_UPDATE_PROFILE, + errorTips, + Notification.NORMAL, app.aaps.core.ui.R.raw.alarm); + long time = System.currentTimeMillis(); + EquilHistoryRecord equilHistoryRecord = new EquilHistoryRecord( + EquilHistoryRecord.EventType.EQUIL_ALARM, + time, + getSerialNumber() + ); + equilHistoryRecord.setResolvedAt(System.currentTimeMillis()); + equilHistoryRecord.setResolvedStatus(ResolvedResult.SUCCESS); + equilHistoryRecord.setNote(errorTips); + equilHistoryRecordDao.insert(equilHistoryRecord); + } + aapsLogger.debug(LTag.PUMPCOMM, "decodeData historyIndex {} errorTips {} port:{} level:{} " + + "parm:{}", + historyIndex, + errorTips, port, level, parm); + setHistoryIndex(historyIndex); + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + calendar.set(Calendar.DAY_OF_MONTH, day); + calendar.set(Calendar.HOUR_OF_DAY, hour); + calendar.set(Calendar.MINUTE, min); + calendar.set(Calendar.SECOND, second); + setLastDataTime(System.currentTimeMillis()); + setCurrentInsulin(insulin); + setBattery(battery); + setRate(rate); + rxBus.send(new EventEquilDataChanged()); + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilResponse.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilResponse.java new file mode 100644 index 00000000000..0c108c72cf3 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/EquilResponse.java @@ -0,0 +1,60 @@ +package app.aaps.pump.equil.manager; + +import java.nio.ByteBuffer; +import java.util.LinkedList; + +/** + * + */ + +public class EquilResponse { + + private final LinkedList send; + private String error_message; + private long delay = 20; + private final long cmdCreateTime; + + public EquilResponse(long cmdCreateTime) { + this.cmdCreateTime = cmdCreateTime; + send = new LinkedList<>(); + } + + public long getCmdCreateTime() { + return cmdCreateTime; + } + + + public boolean hasError() { + return error_message != null; + } + + public void add(ByteBuffer buffer) { + send.add(buffer); + } + + public boolean shouldDelay() { + return delay > 0; + } + + public LinkedList getSend() { + return send; + } + + public String getError_message() { + return error_message; + } + + public void setError_message(String error_message) { + this.error_message = error_message; + } + + public long getDelay() { + return delay; + } + + public void setDelay(long delay) { + this.delay = delay; + } +} + + diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/Utils.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/Utils.java new file mode 100644 index 00000000000..788bd881843 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/Utils.java @@ -0,0 +1,162 @@ +package app.aaps.pump.equil.manager; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.security.SecureRandom; +import java.util.List; + +public class Utils { + public static byte[] generateRandomPassword(int length) { + SecureRandom secureRandom = new SecureRandom(); + byte[] password = new byte[length]; + secureRandom.nextBytes(password); + return password; + } + + public static int bytesToInt(byte highByte, byte lowByte) { + int highValue = (highByte & 0xFF) << 8; + int lowValue = lowByte & 0xFF; + int value = highValue | lowValue; + if (value >= 0x8000) { + return value - 0x8000; + } + return value; + } + + public static float internalDecodeSpeedToUH(int i) { + return new BigDecimal(i).multiply(new BigDecimal("0.00625")).floatValue(); + } + + public static BigDecimal internalDecodeSpeedToUH2(int i) { + return new BigDecimal(i).multiply(new BigDecimal("0.00625")); + } + + public static float decodeSpeedToUH(int i) { + return new BigDecimal(i).multiply(new BigDecimal("0.00625")).floatValue(); + } + + public static double decodeSpeedToUS(int i) { + return internalDecodeSpeedToUH2(i).divide(new BigDecimal("3600"), 10, RoundingMode.DOWN).doubleValue(); + } + + public static int decodeSpeedToUH(double i) { + BigDecimal a = new BigDecimal(String.valueOf(i)); + BigDecimal b = new BigDecimal("0.00625"); + BigDecimal c = a.divide(b); + return c.intValue(); + } + + public static double decodeSpeedToUHT(double i) { + BigDecimal a = new BigDecimal(String.valueOf(i)); + BigDecimal b = new BigDecimal("0.00625"); + return a.divide(b).doubleValue(); + } + + public static byte[] basalToByteArray(double v) { + int value = decodeSpeedToUH(v); + byte[] result = new byte[2]; + result[0] = (byte) ((value >> 8) & 0xFF); // 高位 + result[1] = (byte) (value & 0xFF); // 低位 + return result; + } + + public static byte[] basalToByteArray2(double v) { + int value = decodeSpeedToUH(v); + byte[] result = new byte[2]; + result[1] = (byte) ((value >> 8) & 0xFF); // 高位 + result[0] = (byte) (value & 0xFF); // 低位 + return result; + } + + + private static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + public static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + byte[] result = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + public static byte[] intToBytes(int value) { + byte[] src = new byte[4]; + src[3] = (byte) ((value >> 24) & 0xFF); + src[2] = (byte) ((value >> 16) & 0xFF); + src[1] = (byte) ((value >> 8) & 0xFF); + src[0] = (byte) (value & 0xFF); + return src; + } + public static int bytes2Int(byte[] bytes) { + int int1 = bytes[0] & 0xff; + int int2 = (bytes[1] & 0xff) << 8; + int int3 = (bytes[2] & 0xff) << 16; + int int4 = (bytes[3] & 0xff) << 24; + return int1 | int2 | int3 | int4; + } + public static byte[] intToTwoBytes(int value) { + byte[] bytes = new byte[2]; + bytes[1] = (byte) ((value >> 8) & 0xFF); + bytes[0] = (byte) (value & 0xFF); + return bytes; + + } + + public static byte[] convertByteArray(List byteList) { + byte[] byteArray = new byte[byteList.size()]; + for (int i = 0; i < byteList.size(); i++) { + byteArray[i] = byteList.get(i); + } + return byteArray; + } + + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static String bytesToHex(List bytes) { + if (bytes == null) return ""; + char[] hexChars = new char[bytes.size() * 2]; + for (int j = 0; j < bytes.size(); j++) { + int v = bytes.get(j) & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + public static String bytesToHex(byte[] bytes) { + if (bytes == null) return ""; + final char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + final int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseCmd.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseCmd.java new file mode 100644 index 00000000000..833c6b2cf49 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseCmd.java @@ -0,0 +1,312 @@ +package app.aaps.pump.equil.manager.command; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import app.aaps.core.interfaces.logging.AAPSLogger; +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.core.interfaces.queue.CustomCommand; +import app.aaps.core.interfaces.sharedPreferences.SP; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.database.ResolvedResult; +import app.aaps.pump.equil.manager.Crc; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilManager; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + + +public abstract class BaseCmd implements CustomCommand { + public ResolvedResult resolvedResult = ResolvedResult.NONE; + public static final String defaultPort = "0F0F"; + public static int reqIndex; + public static int pumpReqIndex = 10; + public static int rspIndex = -1; + AAPSLogger aapsLogger; + SP sp; + EquilManager equilManager; + private int timeOut = 22000; + private int connectTimeOut = 15000; + + public String port = "0404"; + public boolean config; + public boolean isEnd; + boolean cmdStatus; + private boolean enacted = true; + public EquilResponse response; + public String runPwd; + public String runCode; + final long createTime; + + public BaseCmd(long createTime) { + this.createTime = createTime; + } + + public abstract EquilResponse getEquilResponse(); + + public abstract EquilResponse getNextEquilResponse(); + + @Nullable public abstract EquilResponse decodeEquilPacket(byte[] data); + + public abstract EquilResponse decode() throws Exception; + + public abstract EquilResponse decodeConfirm() throws Exception; + + public abstract EquilHistoryRecord.EventType getEventType(); + + + public ResolvedResult getResolvedResult() { + return resolvedResult; + } + + public void setResolvedResult(ResolvedResult resolvedResult) { + this.resolvedResult = resolvedResult; + } + + public boolean isEnacted() { + return enacted; + } + + public void setEnacted(boolean enacted) { + this.enacted = enacted; + } + + public int getConnectTimeOut() { + return connectTimeOut; + } + + public void setConnectTimeOut(int connectTimeOut) { + this.connectTimeOut = connectTimeOut; + } + + public int getTimeOut() { + return timeOut; + } + + public void setTimeOut(int timeOut) { + this.timeOut = timeOut; + } + + public boolean isCmdStatus() { + return cmdStatus; + } + + public void setCmdStatus(boolean cmdStatus) { + this.cmdStatus = cmdStatus; + } + + @NonNull @Override public String getStatusDescription() { + return this.getClass().getSimpleName(); + } + + public void setEquilManager(EquilManager equilManager) { + this.equilManager = equilManager; + this.aapsLogger = equilManager.getAapsLogger(); + this.sp = equilManager.getSp(); + } + + + public boolean checkData(byte[] data) { + if (response.getSend().size() > 0) { + byte[] preData = response.getSend().get(response.getSend().size() - 1).array(); + int index = data[3] & 0xff; + int preIndex = preData[3] & 0xff; + if (index == preIndex) { + aapsLogger.debug(LTag.PUMPCOMM, "checkData error "); + return false; + } + } + int crc = data[5] & 0xff; + int crc1 = Crc.CRC8_MAXIM(Arrays.copyOfRange(data, 0, 5)); + if (crc != crc1) { + aapsLogger.debug(LTag.PUMPCOMM, "checkData crc error"); + return false; + } + return true; + } + + + public String getRunCode() { + return runCode; + } + + public void setRunCode(String runCode) { + this.runCode = runCode; + } + + public String getRunPwd() { + return runPwd; + } + + public void setRunPwd(String runPwd) { + this.runPwd = runPwd; + } + + public String getEquilDevices() { + return sp.getString(EquilConst.Prefs.INSTANCE.getEQUIL_DEVICES(), ""); + } + + public String getEquilPassWord() { + return sp.getString(EquilConst.Prefs.INSTANCE.getEQUIL_PASSWORD(), ""); + } + + public static int up1(double value) { + BigDecimal bg = new BigDecimal(value); + return bg.setScale(0, RoundingMode.UP).intValue(); + } + + public boolean isPairStep() { + return false; + } + + public EquilResponse responseCmd(EquilCmdModel equilCmdModel, String port) { + StringBuilder allData = new StringBuilder(); + allData.append(port); + allData.append(equilCmdModel.getTag()); + allData.append(equilCmdModel.getIv()); + allData.append(equilCmdModel.getCiphertext()); + byte[] allByte = Utils.hexStringToBytes(allData.toString()); + byte[] crc1 = Crc.getCRC(allByte); + allByte = Utils.hexStringToBytes(allData.toString()); + int byteIndex = 0; + int lastLen = 0; + int index; + if ((allByte.length - 8) % 10 == 0) { + index = 1; + } else { + index = 2; + } + EquilResponse equilResponse = new EquilResponse(createTime); + int maxLen = up1((allByte.length - 8) / 10) + index; + for (int i = 0; i < maxLen; i++) { + ByteBuffer buffer = ByteBuffer.allocate(16); + if (i > 0 && lastLen < 10) { + buffer = ByteBuffer.allocate(6 + lastLen); + + } + buffer.put((byte) 0x00); + buffer.put((byte) 0x00); + if (i == maxLen - 1) { + buffer.put((byte) (6 + lastLen)); + buffer.put((byte) ((10 * i))); + buffer.put((byte) toNewEndConf((byte) reqIndex)); + } else { + buffer.put((byte) 0x10); + buffer.put((byte) (10 * i)); + buffer.put((byte) toNewStart((byte) reqIndex)); + } + byte[] crcArray = new byte[5]; + System.arraycopy(buffer.array(), 0, crcArray, 0, 5); + buffer.put((byte) Crc.CRC8_MAXIM(crcArray)); + if (i == 0) { + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put(crc1[1]); + buffer.put(crc1[0]); + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + } else { + if (lastLen < 10) { + for (int j = 0; j < lastLen; j++) { + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + } + } else { + for (int j = 0; j < 10; j++) { + buffer.put((byte) allByte[byteIndex]); + byteIndex++; + } + } + } + lastLen = allByte.length - byteIndex; + equilResponse.add(buffer); + } + reqIndex++; + return equilResponse; + } + + + public EquilCmdModel decodeModel() { + EquilCmdModel equilCmdModel = new EquilCmdModel(); + List list = new ArrayList<>(); + int index = 0; + for (ByteBuffer b : response.getSend()) { + if (index == 0) { + byte[] bs = b.array(); + for (int i = bs.length - 4; i < bs.length; i++) { + list.add((Byte) bs[i]); + } + byte[] codeByte = new byte[]{bs[10], bs[11]}; + equilCmdModel.setCode(Utils.bytesToHex(codeByte)); + } else { + byte[] bs = b.array(); + for (int i = 6; i < bs.length; i++) { + list.add((Byte) bs[i]); + } + } + index++; + } + List list1 = list.subList(0, 16); + List list2 = list.subList(16, 12 + 16); + List list3 = list.subList(12 + 16, list.size()); + equilCmdModel.setIv(Utils.bytesToHex(list2).toLowerCase()); + equilCmdModel.setTag(Utils.bytesToHex(list1).toLowerCase()); + equilCmdModel.setCiphertext(Utils.bytesToHex(list3).toLowerCase()); + return equilCmdModel; + } + + public byte toNewStart(byte number) { + number &= ~(1 << 7); // 清除指定位(设置为0) + return number; + } + + public byte toNewEndConf(byte number) { +// number &= ~(1 << 6); // 清除指定位(设置为0) + number |= (1 << 7); // 设置指定位为1 + return number; + } + + public boolean isEnd(byte b) { + int bit = getBit(b, 7); + return bit == 1; + } + + public int getIndex(byte b) { + int result = (b & 63); // 提取前6位并右移2位 + return result; + } + + public int getBit(byte b, int i) { + int bit = (int) ((b >> i) & 0x1); + return bit; + } + + public String convertString(String input) { + StringBuilder sb = new StringBuilder(); + for (char ch : input.toCharArray()) { + sb.append("0").append(ch); + } + return sb.toString(); + } + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseSetting.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseSetting.java new file mode 100644 index 00000000000..c66e46f002f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/BaseSetting.java @@ -0,0 +1,144 @@ +package app.aaps.pump.equil.manager.command; + +import java.nio.ByteBuffer; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.manager.AESUtil; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + + +public abstract class BaseSetting extends BaseCmd { + + public BaseSetting(long createTime) { + super(createTime); + } + + public byte[] getReqData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] tzm = Utils.hexStringToBytes(getEquilDevices()); + byte[] data = Utils.concat(indexByte, tzm); + pumpReqIndex++; + return data; + } + + + @Override + public EquilResponse getEquilResponse() { + config = false; + isEnd = false; + response = new EquilResponse(createTime); + byte[] pwd = Utils.hexStringToBytes(getEquilPassWord()); + byte[] data = getReqData(); + EquilCmdModel equilCmdModel; + try { + equilCmdModel = AESUtil.aesEncrypt(pwd, data); + return responseCmd(equilCmdModel, defaultPort + "0000"); + } catch (Exception e) { + synchronized (this) { + setCmdStatus(false); + } + } + return null; + } + + + public EquilResponse decodeEquilPacket(byte[] data) { + if (!checkData(data)) { + return null; + } + byte code = data[4]; + int intValue = getIndex(code); + if (config) { + if (rspIndex == intValue) { + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse response1 = decodeConfirm(); + isEnd = true; + response = new EquilResponse(createTime); + rspIndex = intValue; + return response1; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeEquilPacket error =====" + e.getMessage()); + } + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decode(); + response = new EquilResponse(createTime); + config = true; + rspIndex = intValue; + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeEquilPacket error=====" + e.getMessage()); + } + return null; + + } + + @Override + public EquilResponse decode() throws Exception { + EquilCmdModel reqModel = decodeModel(); + byte[] pwd = Utils.hexStringToBytes(getEquilPassWord()); + String content = AESUtil.decrypt(reqModel, pwd); + String pwd2 = content.substring(8); + runPwd = pwd2; + byte[] data = getFirstData(); + EquilCmdModel equilCmdModel = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + runCode = reqModel.getCode(); + return responseCmd(equilCmdModel, port + runCode); + } + + public EquilResponse getNextEquilResponse() { + aapsLogger.debug(LTag.PUMPCOMM, "getNextEquilResponse=== start11 "); + config = true; + isEnd = false; + response = new EquilResponse(createTime); + byte[] data = getFirstData(); + EquilCmdModel equilCmdModel; + try { + aapsLogger.debug(LTag.PUMPCOMM, "getNextEquilResponse=== start "); + equilCmdModel = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + return responseCmd(equilCmdModel, port + runCode); + } catch (Exception e) { + aapsLogger.debug(LTag.PUMPCOMM, "getNextEquilResponse===" + e.getMessage()); + synchronized (this) { + setCmdStatus(false); + } + } + return null; + } + + + public abstract byte[] getFirstData(); + + public abstract byte[] getNextData(); + + public abstract void decodeConfirmData(byte[] data); + + public EquilResponse decodeConfirm() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + runCode = equilCmdModel.getCode(); + String content = AESUtil.decrypt(equilCmdModel, Utils.hexStringToBytes(runPwd)); + decodeConfirmData(Utils.hexStringToBytes(content)); + byte[] data = getNextData(); + EquilCmdModel equilCmdModel2 = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + return responseCmd(equilCmdModel2, port + runCode); + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdAlarmSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdAlarmSet.java new file mode 100644 index 00000000000..c553e3575cc --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdAlarmSet.java @@ -0,0 +1,64 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.data.AlarmMode; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +public class CmdAlarmSet extends BaseSetting { + + public CmdAlarmSet(int mode) { + super(System.currentTimeMillis()); + this.mode = mode; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x0b}; + byte[] data3 = Utils.intToBytes(mode); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x0b, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override + public void decodeConfirmData(byte[] data) { + synchronized (this) { + setCmdStatus(true); + notifyAll(); + } + } + + + int mode; + + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + if (mode == AlarmMode.MUTE.getCommand()) { + return EquilHistoryRecord.EventType.SET_ALARM_MUTE; + } else if (mode == AlarmMode.TONE.getCommand()) { + return EquilHistoryRecord.EventType.SET_ALARM_TONE; + } else if (mode == AlarmMode.TONE_AND_SHAKE.getCommand()) { + return EquilHistoryRecord.EventType.SET_ALARM_TONE_AND_SHAK; + } else if (mode == AlarmMode.SHAKE.getCommand()) { + return EquilHistoryRecord.EventType.SET_ALARM_SHAKE; + } + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalGet.java new file mode 100644 index 00000000000..3befd2094a2 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalGet.java @@ -0,0 +1,64 @@ +package app.aaps.pump.equil.manager.command; + + +import java.util.Arrays; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.core.interfaces.profile.Profile; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdBasalGet extends BaseSetting { + + Profile profile; + + public CmdBasalGet(Profile profile) { + super(System.currentTimeMillis()); + this.profile = profile; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x02}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x02, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + aapsLogger.debug(LTag.PUMPCOMM, "CmdBasalGet==" + Utils.bytesToHex(data)); + StringBuilder currentBasal = new StringBuilder(); + if (profile != null) { + for (int i = 0; i < 24; i++) { + double value = profile.getBasalTimeFromMidnight(i * 60 * 60); + value = value / 2f; + byte[] bs = Utils.basalToByteArray2(value); + currentBasal.append(Utils.bytesToHex(bs)); + currentBasal.append(Utils.bytesToHex(bs)); + } + } + byte[] rspByte = Arrays.copyOfRange(data, 6, data.length); + String rspBasal = Utils.bytesToHex(rspByte); + aapsLogger.debug(LTag.PUMPCOMM, + "CmdBasalGet==" + currentBasal + "====\n==" + rspBasal); + synchronized (this) { + setCmdStatus(true); + setEnacted(currentBasal.toString().equals(rspBasal)); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalSet.java new file mode 100644 index 00000000000..fc6d2b3464a --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdBasalSet.java @@ -0,0 +1,88 @@ +package app.aaps.pump.equil.manager.command; + + +import java.util.ArrayList; +import java.util.List; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.core.interfaces.profile.Profile; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.driver.definition.BasalSchedule; +import app.aaps.pump.equil.driver.definition.BasalScheduleEntry; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdBasalSet extends BaseSetting { + Profile profile; + + public Profile getProfile() { + return profile; + } + + public void setProfile(Profile profile) { + this.profile = profile; + } + + BasalSchedule basalSchedule; + + public CmdBasalSet(BasalSchedule basalSchedule, Profile profile) { + super(System.currentTimeMillis()); + this.profile = profile; + this.basalSchedule = basalSchedule; + + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x02}; + List list = new ArrayList<>(); + int i = 0; + for (BasalScheduleEntry basalScheduleEntry : basalSchedule.getEntries()) { + double rate = basalScheduleEntry.getRate(); + double value = rate / 2f; + byte[] bs = Utils.basalToByteArray(value); + aapsLogger.debug(LTag.PUMPCOMM, + i + "==CmdBasalSet==" + value + "====" + rate + "===" + Utils.decodeSpeedToUH(value) + "===" + + Utils.decodeSpeedToUHT(value)); + list.add(bs[1]); + list.add(bs[0]); + list.add(bs[1]); + list.add(bs[0]); + i++; + } + String hex = Utils.bytesToHex(list); + byte[] data = Utils.concat(indexByte, data2, Utils.hexStringToBytes(hex)); + pumpReqIndex++; + return data; + } + + public static byte[] intToByteArray(double v) { + int value = (int) (v / 0.025f * 4); + byte[] result = new byte[2]; + result[0] = (byte) ((value >> 8) & 0xFF); // 高位 + result[1] = (byte) (value & 0xFF); // 低位 + return result; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x02, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + synchronized (this) { + sp.putBoolean(EquilConst.Prefs.INSTANCE.getEQUIL_BASAL_SET(), true); + setCmdStatus(true); + notifyAll(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.SET_BASAL_PROFILE; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesGet.java new file mode 100644 index 00000000000..f44cf23f245 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesGet.java @@ -0,0 +1,49 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdDevicesGet extends BaseSetting { + public CmdDevicesGet() { + super(System.currentTimeMillis()); + port = "0000"; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x00}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x00, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override + public void decodeConfirmData(byte[] data) { + int value = Utils.bytesToInt(data[7], data[6]); + + String firmwareVersion = data[18] + "." + data[19]; + aapsLogger.debug(LTag.PUMPCOMM, "CmdGetDevices====" + + Utils.bytesToHex(data) + "=====" + value + "===" + firmwareVersion); + equilManager.setFirmwareVersion(firmwareVersion); + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.READ_DEVICES; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesOldGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesOldGet.java new file mode 100644 index 00000000000..c6747a9ee35 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdDevicesOldGet.java @@ -0,0 +1,186 @@ +package app.aaps.pump.equil.manager.command; + + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdDevicesOldGet extends BaseSetting { + public String address; + private float firmwareVersion; + + public CmdDevicesOldGet(String address) { + super(System.currentTimeMillis()); + port = "0E0E"; + this.address = address; + } + + public String getAddress() { + return address; + } + + public EquilResponse getEquilResponse() { + config = false; + isEnd = false; + response = new EquilResponse(createTime); + EquilResponse temp = new EquilResponse(createTime); + ByteBuffer buffer = ByteBuffer.allocate(14); + buffer.put((byte) 0x00); + buffer.put((byte) 0x00); + buffer.put((byte) 0x0E); + buffer.put((byte) 0x00); + buffer.put((byte) 0x80); + buffer.put((byte) 0x78); + buffer.put((byte) 0x00); + buffer.put((byte) 0x00); + buffer.put((byte) 0x00); + buffer.put((byte) 0x00); + buffer.put((byte) 0x01); + buffer.put((byte) 0x7B); + buffer.put((byte) 0x02); + buffer.put((byte) 0x00); + temp.add(buffer); + return temp; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x00}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x00, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public EquilResponse decodeEquilPacket(byte[] data) { + if (!checkData(data)) { + return null; + } + byte code = data[4]; + int intValue = getIndex(code); + if (config) { + if (rspIndex == intValue) { + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decodeConfirm(); + isEnd = true; + response = new EquilResponse(createTime); + rspIndex = intValue; + aapsLogger.debug(LTag.PUMPCOMM, "intValue=====" + intValue + "====" + rspIndex); + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeConfirm error =====" + e.getMessage()); + + } + + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decode(); + response = new EquilResponse(createTime); + config = true; + rspIndex = intValue; + aapsLogger.debug(LTag.PUMPCOMM, "intValue=====" + intValue + "====" + rspIndex); + return list; + } catch (Exception e) { + e.printStackTrace(); + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decode error=====" + e.getMessage()); + } + return null; + + } + + public EquilResponse decode() { + EquilCmdModel reqModel = decodeModel(); + byte[] data = Utils.hexStringToBytes(reqModel.getCiphertext()); + String fv = data[12] + "." + data[13]; + firmwareVersion = Float.parseFloat(fv); + aapsLogger.debug(LTag.PUMPCOMM, "CmdDevicesOldGet====" + + Utils.bytesToHex(data) + "========" + firmwareVersion + "===" + (firmwareVersion < EquilConst.EQUIL_SUPPORT_LEVEL)); + reqModel.setCiphertext(Utils.bytesToHex(getNextData())); + synchronized (this) { + setCmdStatus(true); + notify(); + } + return responseCmd(reqModel, "0000" + reqModel.getCode()); + } + + public EquilCmdModel decodeModel() { + EquilCmdModel equilCmdModel = new EquilCmdModel(); + List list = new ArrayList<>(); + int index = 0; + for (ByteBuffer b : response.getSend()) { + if (index == 0) { + byte[] bs = b.array(); + byte[] codeByte = new byte[]{bs[10], bs[11]}; + list.add((Byte) bs[bs.length - 2]); + list.add((Byte) bs[bs.length - 1]); + equilCmdModel.setCode(Utils.bytesToHex(codeByte)); + } else { + byte[] bs = b.array(); + for (int i = 6; i < bs.length; i++) { + list.add((Byte) bs[i]); + } + } + index++; + } + List list3 = list.subList(0, list.size()); + equilCmdModel.setCiphertext(Utils.bytesToHex(list3).toLowerCase()); + equilCmdModel.setTag(""); + equilCmdModel.setIv(""); + return equilCmdModel; + } + + @Override + public void decodeConfirmData(byte[] data) { + int value = Utils.bytesToInt(data[7], data[6]); + String fv = data[18] + "." + data[19]; + firmwareVersion = Float.parseFloat(fv); + aapsLogger.debug(LTag.PUMPCOMM, "CmdDevicesOldGet====" + + Utils.bytesToHex(data) + "=====" + value + "===" + firmwareVersion + "====" + + (firmwareVersion < EquilConst.EQUIL_SUPPORT_LEVEL)); + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + public boolean isSupport() { + return !(firmwareVersion < EquilConst.EQUIL_SUPPORT_LEVEL); + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdExtendedBolusSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdExtendedBolusSet.java new file mode 100644 index 00000000000..8431eb957ca --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdExtendedBolusSet.java @@ -0,0 +1,87 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; +import app.aaps.pump.equil.manager.Utils; + +public class CmdExtendedBolusSet extends BaseSetting { + double insulin; + int step; + int pumpTime; + int durationInMinutes; + boolean cancel; + + public boolean isCancel() { + return cancel; + } + + public void setCancel(boolean cancel) { + this.cancel = cancel; + } + + public double getInsulin() { + return insulin; + } + + public void setInsulin(double insulin) { + this.insulin = insulin; + } + + public int getDurationInMinutes() { + return durationInMinutes; + } + + public void setDurationInMinutes(int durationInMinutes) { + this.durationInMinutes = durationInMinutes; + } + + public CmdExtendedBolusSet(double insulin, int durationInMinutes, boolean cancel) { + super(System.currentTimeMillis()); + this.insulin = insulin; + this.durationInMinutes = durationInMinutes; + this.cancel = cancel; + if (insulin != 0) { + step = (int) (insulin / 0.05d * 8); + this.pumpTime = durationInMinutes * 60; + } + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x03}; + byte[] data3 = Utils.intToBytes(step); + byte[] data4 = Utils.intToBytes(pumpTime); + byte[] data5 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data5, data5, data3, data4); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x03, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; + int status = data[6] & 0xff; + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + if (cancel) { + return EquilHistoryRecord.EventType.CANCEL_EXTENDED_BOLUS; + } else { + return EquilHistoryRecord.EventType.SET_EXTENDED_BOLUS; + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdHistoryGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdHistoryGet.java new file mode 100644 index 00000000000..71281d3ddee --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdHistoryGet.java @@ -0,0 +1,125 @@ +package app.aaps.pump.equil.manager.command; + +import androidx.annotation.NonNull; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdHistoryGet extends BaseSetting { + + private int battery; + private int medicine; + private int rate; + private int largeRate; + private int year; + private int month; + private int day; + private int hour; + private int min; + private int second; + private int index; + // private int port; + private int type; + private int level; + private int parm; + private int currentIndex = 0; + private int resultIndex; + + public CmdHistoryGet() { + super(System.currentTimeMillis()); + this.port = "0505"; + } + + public CmdHistoryGet(int currentIndex) { + this(); + this.currentIndex = currentIndex; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x01}; + byte[] data3 = Utils.intToBytes(currentIndex); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + aapsLogger.debug(LTag.PUMPCOMM, "getReqData2===" + Utils.bytesToHex(data)); + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x01, 0x01}; + aapsLogger.debug(LTag.PUMPCOMM, "currentIndex===" + currentIndex); + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { +// 67631679050017070e101319 + int index1 = data[4]; + year = data[6] & 0xff; + month = data[7] & 0xff; + day = data[8] & 0xff; + hour = data[9] & 0xff; + min = data[10] & 0xff; + second = data[11] & 0xff; + //a5e207590501 17070e100f161100000000007d0204080000 + //ae6ae9100501 17070e100f16 1100000000007d0204080000 + battery = data[12] & 0xff; + medicine = data[13] & 0xff; + rate = Utils.bytesToInt(data[15], data[14]); + largeRate = Utils.bytesToInt(data[17], data[16]); + index = Utils.bytesToInt(data[19], data[18]); +// port = data[20] & 0xff; + type = data[21] & 0xff; + level = data[22] & 0xff; + parm = data[23] & 0xff; + if (currentIndex != 0) { + equilManager.decodeHistory(data); + } + resultIndex = index; + aapsLogger.debug(LTag.PUMPCOMM, "history index==" + index + "===" + Utils.bytesToHex(data) + + "===" + rate + "====" + largeRate + "===" + Utils.bytesToHex(new byte[]{data[16], + data[17]})); + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @NonNull @Override + public String toString() { + return "CmdHistoryGet{" + + "battery=" + battery + + ", medicine=" + medicine + + ", rate=" + rate + + ", largeRate=" + largeRate + + ", year=" + year + + ", month=" + month + + ", day=" + day + + ", hour=" + hour + + ", min=" + min + + ", second=" + second + + ", index=" + index + + ", port=" + port + + ", type=" + type + + ", level=" + level + + ", parm=" + parm + + '}'; + } + + public int getCurrentIndex() { + return resultIndex; + } + + public void setCurrentIndex(int currentIndex) { + this.currentIndex = currentIndex; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinChange.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinChange.java new file mode 100644 index 00000000000..f6584d97d4e --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinChange.java @@ -0,0 +1,48 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdInsulinChange extends BaseSetting { + + public CmdInsulinChange() { + super(System.currentTimeMillis()); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x06}; + byte[] data3 = Utils.intToBytes(32000); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x06, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { +// byte[] byteData = Crc.hexStringToBytes(data); + int status = data[6] & 0xff; + synchronized (this) { + setCmdStatus(true); + notify(); + } + equilManager.setInsulinChange(status); + aapsLogger.debug(LTag.PUMPCOMM, "status====" + status + "====" + Utils.bytesToHex(data)); + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.CHANGE_INSULIN; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinGet.java new file mode 100644 index 00000000000..cac34293582 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdInsulinGet.java @@ -0,0 +1,47 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +public class CmdInsulinGet extends BaseSetting { + + public CmdInsulinGet() { + super(System.currentTimeMillis()); + this.port = "0505"; + + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x07}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x07, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int insulin = data[6] & 0xff; + equilManager.setStartInsulin(insulin); + equilManager.setCurrentInsulin(insulin); + synchronized (this) { + setCmdStatus(true); + notify(); + } + + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdLargeBasalSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdLargeBasalSet.java new file mode 100644 index 00000000000..3e75b219038 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdLargeBasalSet.java @@ -0,0 +1,73 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdLargeBasalSet extends BaseSetting { + double insulin; + int step; + int stepTime; + + public int getStepTime() { + return stepTime; + } + + public double getInsulin() { + return insulin; + } + + public void setInsulin(double insulin) { + this.insulin = insulin; + } + + public CmdLargeBasalSet(double insulin) { + super(System.currentTimeMillis()); + this.insulin = insulin; + if (insulin != 0) { + step = (int) (insulin / 0.05d * 8); + stepTime = (int) (insulin / 0.05d * 2); + } + } + + @Override + public byte[] getFirstData() { + aapsLogger.debug(LTag.PUMPCOMM, "step===" + step + "=====" + stepTime); + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x03}; + byte[] data3 = Utils.intToBytes(step); + byte[] data4 = Utils.intToBytes(stepTime); + byte[] data5 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3, data4, data5, data5); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x03, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; + int status = data[6] & 0xff; + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + if (insulin == 0) { + return EquilHistoryRecord.EventType.CANCEL_BOLUS; + } else { + return EquilHistoryRecord.EventType.SET_BOLUS; + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelGet.java new file mode 100644 index 00000000000..bbb1380282d --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelGet.java @@ -0,0 +1,47 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdModelGet extends BaseSetting { + public CmdModelGet() { + super(System.currentTimeMillis()); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x00}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x02, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override + public void decodeConfirmData(byte[] data) { + + int mode = data[6] & 0xff; + aapsLogger.debug(LTag.PUMPCOMM, "CmdGetModel====" + mode); + equilManager.setModel(mode); + cmdStatus = true; + synchronized (this) { + notify(); + } + + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelSet.java new file mode 100644 index 00000000000..003ff08f88d --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdModelSet.java @@ -0,0 +1,66 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.data.RunMode; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +public class CmdModelSet extends BaseSetting { + public RunMode getMode() { + if (mode == 0) { + return RunMode.SUSPEND; + } else if (mode == 1) { + return RunMode.RUN; + } else if (mode == 2) { + return RunMode.RUN; + } else { + return RunMode.SUSPEND; + } + } + + public void setMode(int mode) { + this.mode = mode; + } + + int mode; + + public CmdModelSet(int mode) { + super(System.currentTimeMillis()); + this.mode = mode; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x00}; + byte[] data3 = Utils.intToBytes(mode); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x00, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + RunMode runMode = getMode(); + if (runMode == RunMode.RUN) { + return EquilHistoryRecord.EventType.RESUME_DELIVERY; + } else if (runMode == RunMode.SUSPEND) { + return EquilHistoryRecord.EventType.SUSPEND_DELIVERY; + } + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdPair.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdPair.java new file mode 100644 index 00000000000..07f94ff4bfe --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdPair.java @@ -0,0 +1,165 @@ +package app.aaps.pump.equil.manager.command; + + +import android.util.Log; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.AESUtil; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdPair extends BaseCmd { + public static final String ERROR_PWD = + "0000000000000000000000000000000000000000000000000000000000000000"; + String sn; + public String address; + private final String password; + + public CmdPair(String name, String address, String password) { + super(System.currentTimeMillis()); + port = "0E0E"; + this.address = address; + this.password = password; + sn = name.replace("Equil - ", "").trim(); + sn = convertString(sn); + Log.e(LTag.PUMPCOMM.toString(), "sn===" + sn); + } + + public String getAddress() { + return address; + } + + byte[] randomPassword; + + @Override + public EquilResponse getEquilResponse() { + response = new EquilResponse(createTime); + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(Utils.hexStringToBytes(sn)); + byte[] pwd = messageDigest.digest(); + + //B0EB6308060F79D685D6269DC048E32E4C103CD2B8EEA2DE4637EB8A5D6BCD08 + byte[] equilPassword = AESUtil.getEquilPassWord(password); + + + randomPassword = Utils.generateRandomPassword(32); + byte[] data = Utils.concat(equilPassword, randomPassword); + aapsLogger.debug(LTag.PUMPCOMM, "pwd==" + Utils.bytesToHex(pwd)); + aapsLogger.debug(LTag.PUMPCOMM, "data==" + Utils.bytesToHex(data)); + EquilCmdModel equilCmdModel = AESUtil.aesEncrypt(pwd, data); + return responseCmd(equilCmdModel, "0D0D0000"); + } catch (Exception e) { + e.printStackTrace(); + } + + + return null; + } + + @Override public EquilResponse getNextEquilResponse() { + return getEquilResponse(); + } + + + public EquilResponse decodeEquilPacket(byte[] data) { + if (!checkData(data)) { + return null; + } + byte code = data[4]; + int intValue = getIndex(code); + if (config) { + if (rspIndex == intValue) { + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decodeConfirm(); + isEnd = true; + response = new EquilResponse(createTime); + rspIndex = intValue; + aapsLogger.debug(LTag.PUMPCOMM, "intValue=====" + intValue + "====" + rspIndex); + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeConfirm error =====" + e.getMessage()); + + } + + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decode(); + response = new EquilResponse(createTime); + config = true; + rspIndex = intValue; + aapsLogger.debug(LTag.PUMPCOMM, "intValue=====" + intValue + "====" + rspIndex); + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decode error=====" + e.getMessage()); + } + return null; + + } + + @Override + public EquilResponse decode() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + byte[] keyBytes = randomPassword; + String content = AESUtil.decrypt(equilCmdModel, keyBytes); + String pwd1 = content.substring(0, 64); + String pwd2 = content.substring(64); + aapsLogger.debug(LTag.PUMPCOMM, "decrypted====" + pwd1); + aapsLogger.debug(LTag.PUMPCOMM, "decrypted====" + pwd2); + if (ERROR_PWD.equals(pwd1) && ERROR_PWD.equals(pwd2)) { + synchronized (this) { + setCmdStatus(true); + setEnacted(false); + notifyAll(); + } + return null; + } + + sp.putString(EquilConst.Prefs.INSTANCE.getEQUIL_PASSWORD(), pwd2); + sp.putString(EquilConst.Prefs.INSTANCE.getEQUIL_DEVICES(), pwd1); + runPwd = pwd2; + byte[] data1 = Utils.hexStringToBytes(pwd1); + byte[] data = Utils.concat(data1, keyBytes); + EquilCmdModel equilCmdModel2 = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + runCode = equilCmdModel.getCode(); + return responseCmd(equilCmdModel2, port + runCode); + } + + + @Override + public EquilResponse decodeConfirm() { + synchronized (this) { + setCmdStatus(true); + notifyAll(); + } + return null; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.INITIALIZE_EQUIL; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdResistanceGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdResistanceGet.java new file mode 100644 index 00000000000..cbc7a5a2b79 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdResistanceGet.java @@ -0,0 +1,61 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.AESUtil; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + +public class CmdResistanceGet extends BaseSetting { + public CmdResistanceGet() { + super(System.currentTimeMillis()); + port = "1515"; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x02}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x02, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override + public void decodeConfirmData(byte[] data) { + int value = Utils.bytesToInt(data[7], data[6]); + cmdStatus = true; + if (value < 500) { + setEnacted(false); + } else { + setEnacted(true); + } + synchronized (this) { + notify(); + } + } + + public EquilResponse decodeConfirm() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + runCode = equilCmdModel.getCode(); + String content = AESUtil.decrypt(equilCmdModel, Utils.hexStringToBytes(runPwd)); + decodeConfirmData(Utils.hexStringToBytes(content)); + byte[] data = getNextData(); + EquilCmdModel equilCmdModel2 = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); +// return responseCmd(equilCmdModel2, port + runCode,true); + return null; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingGet.java new file mode 100644 index 00000000000..0bb46467a6e --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingGet.java @@ -0,0 +1,137 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdSettingGet extends BaseSetting { + private long useTime; + private long closeTime; + private float lowAlarm; + private float largefastAlarm; + private float stopAlarm; + private float infusionUnit; + private float basalAlarm; + private float largeAlarm; + + public CmdSettingGet() { + super(System.currentTimeMillis()); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x05}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x05, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; +// int i1 = ((data[15] & 0x0f) << 8) | data[14] & 0xff; +// int i2 = ((data[17] & 0x0f) << 8) | data[16] & 0xff; +// int i3 = ((data[19] & 0x0f) << 8) | data[18] & 0xff; +// int i4 = ((data[21] & 0x0f) << 8) | data[20] & 0xff; +// int i5 = ((data[23] & 0x0f) << 8) | data[22] & 0xff; +// int i6 = ((data[25] & 0x0f) << 8) | data[24] & 0xff; + + + int i1 = Utils.bytesToInt(data[15], data[14]); + int i2 = Utils.bytesToInt(data[17], data[16]); + int i3 = Utils.bytesToInt(data[19], data[18]); + int i4 = Utils.bytesToInt(data[21], data[20]); + int i5 = Utils.bytesToInt(data[23], data[22]); + int i6 = Utils.bytesToInt(data[25], data[24]); + lowAlarm = Utils.internalDecodeSpeedToUH(i1); + largefastAlarm = Utils.internalDecodeSpeedToUH(i2); + stopAlarm = Utils.internalDecodeSpeedToUH(i3); + infusionUnit = Utils.internalDecodeSpeedToUH(i4); + basalAlarm = Utils.internalDecodeSpeedToUH(i5); + largeAlarm = Utils.internalDecodeSpeedToUH(i6); + aapsLogger.debug(LTag.PUMPCOMM, + "CmdSettingGet===" + Utils.bytesToHex(data) + "====" + lowAlarm + "=======" + i1); + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + public long getUseTime() { + return useTime; + } + + public void setUseTime(long useTime) { + this.useTime = useTime; + } + + public long getCloseTime() { + return closeTime; + } + + public void setCloseTime(long closeTime) { + this.closeTime = closeTime; + } + + public float getLowAlarm() { + return lowAlarm; + } + + public void setLowAlarm(float lowAlarm) { + this.lowAlarm = lowAlarm; + } + + public float getLargefastAlarm() { + return largefastAlarm; + } + + public void setLargefastAlarm(float largefastAlarm) { + this.largefastAlarm = largefastAlarm; + } + + public float getStopAlarm() { + return stopAlarm; + } + + public void setStopAlarm(float stopAlarm) { + this.stopAlarm = stopAlarm; + } + + public float getInfusionUnit() { + return infusionUnit; + } + + public void setInfusionUnit(float infusionUnit) { + this.infusionUnit = infusionUnit; + } + + public float getBasalAlarm() { + return basalAlarm; + } + + public void setBasalAlarm(float basalAlarm) { + this.basalAlarm = basalAlarm; + } + + public float getLargeAlarm() { + return largeAlarm; + } + + public void setLargeAlarm(float largeAlarm) { + this.largeAlarm = largeAlarm; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingSet.java new file mode 100644 index 00000000000..67e5345ea17 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdSettingSet.java @@ -0,0 +1,82 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.EquilConst; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdSettingSet extends BaseSetting { + double lowAlarm; + + int bolusThresholdStep = EquilConst.EQUIL_BLOUS_THRESHOLD_STEP; + + public CmdSettingSet() { + super(System.currentTimeMillis()); + } + + public CmdSettingSet(double maxBolus) { + super(System.currentTimeMillis()); + bolusThresholdStep = Utils.decodeSpeedToUH(maxBolus); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] equilCmd = new byte[]{0x01, 0x05}; + byte[] useTime = Utils.intToBytes(0); + byte[] autoCloseTime = Utils.intToBytes(0); + byte[] lowAlarmByte = Utils.intToTwoBytes(bolusThresholdStep); + byte[] fastBolus = Utils.intToTwoBytes(0); + byte[] occlusion = Utils.intToTwoBytes(2800); + byte[] insulinUnit = Utils.intToTwoBytes(8); + byte[] basalThreshold = Utils.intToTwoBytes(240); + byte[] bolusThreshold = Utils.intToTwoBytes(1600); + byte[] data = Utils.concat(indexByte, equilCmd, useTime, autoCloseTime, + lowAlarmByte, fastBolus, + occlusion, insulinUnit, basalThreshold, bolusThreshold); + pumpReqIndex++; + aapsLogger.debug(LTag.PUMPCOMM, + "CmdSettingSet data===" + Utils.bytesToHex(data) + "====" + lowAlarm + "===" + Utils.decodeSpeedToUH(lowAlarm)); + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x05, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; +// int i1 = ((data[15] & 0x0f) << 8) | data[14] & 0xff; +// int i2 = ((data[17] & 0x0f) << 8) | data[16] & 0xff; +// int i3 = ((data[19] & 0x0f) << 8) | data[18] & 0xff; +// int i4 = ((data[21] & 0x0f) << 8) | data[20] & 0xff; +// int i5 = ((data[23] & 0x0f) << 8) | data[22] & 0xff; +// int i6 = ((data[25] & 0x0f) << 8) | data[24] & 0xff; +// lowAlarm = Utils.internalDecodeSpeedToUH(i1); +// largefastAlarm = Utils.internalDecodeSpeedToUH(i2); +// stopAlarm = Utils.internalDecodeSpeedToUH(i3); +// infusionUnit = Utils.internalDecodeSpeedToUH(i4); +// basalAlarm = Utils.internalDecodeSpeedToUH(i5); +// largeAlarm = Utils.internalDecodeSpeedToUH(i6); +// aapsLogger.debug(LTag.PUMPCOMM, +// "CmdSettingSet===" + Crc.bytesToHex(data) + "====" + lowAlarm); + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + public boolean isPairStep() { + return true; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStatusGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStatusGet.java new file mode 100644 index 00000000000..436f85e8d71 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStatusGet.java @@ -0,0 +1,13 @@ +package app.aaps.pump.equil.manager.command; + + +import androidx.annotation.NonNull; + +import app.aaps.core.interfaces.queue.CustomCommand; + + +public class CmdStatusGet implements CustomCommand { + @NonNull @Override public String getStatusDescription() { + return "CmdStatusGet"; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStepSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStepSet.java new file mode 100644 index 00000000000..288e5ce0375 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdStepSet.java @@ -0,0 +1,62 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.AESUtil; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + +public class CmdStepSet extends BaseSetting { + boolean sendConfig; + int step; + + public CmdStepSet(boolean sendConfig, int step) { + super(System.currentTimeMillis()); + this.sendConfig = sendConfig; + this.step = step; + } + + + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x07}; + byte[] data3 = Utils.intToBytes(step); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x07, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { +// byte[] byteData = Crc.hexStringToBytes(data); +// int status = byteData[6] & 0xff; + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + public EquilResponse decodeConfirm() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + runCode = equilCmdModel.getCode(); + String content = AESUtil.decrypt(equilCmdModel, Utils.hexStringToBytes(runPwd)); + decodeConfirmData(Utils.hexStringToBytes(content)); + byte[] data = getNextData(); + EquilCmdModel equilCmdModel2 = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + if (sendConfig) { + return responseCmd(equilCmdModel2, port + runCode); + } + return null; + } + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalGet.java new file mode 100644 index 00000000000..ce3aea17fa8 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalGet.java @@ -0,0 +1,66 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdTempBasalGet extends BaseSetting { + private int time; + private int step; + + public int getTime() { + return time; + } + + public void setTime(int time) { + this.time = time; + } + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public CmdTempBasalGet() { + super(System.currentTimeMillis()); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x04}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x04, 0x02}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; + step = Utils.bytes2Int(new byte[]{data[6], data[7], data[8], data[9]}); + time = Utils.bytes2Int(new byte[]{data[10], data[11], data[12], data[13]}); + aapsLogger.debug(LTag.PUMPCOMM, "CmdTempBasalGet===" + step + "====" + time); +// Utils.by + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalSet.java new file mode 100644 index 00000000000..204318ea3de --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTempBasalSet.java @@ -0,0 +1,89 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdTempBasalSet extends BaseSetting { + double insulin; + int duration; + int step; + int pumpTime; + boolean cancel; + + public double getInsulin() { + return insulin; + } + + public void setInsulin(double insulin) { + this.insulin = insulin; + } + + public int getDuration() { + return duration; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public boolean isCancel() { + return cancel; + } + + public void setCancel(boolean cancel) { + this.cancel = cancel; + } + + public CmdTempBasalSet(double insulin, int duration) { + super(System.currentTimeMillis()); + this.insulin = insulin; + this.duration = duration; + if (insulin != 0) { + step = (int) (insulin / 0.05d * 8) / 2; + } else { + step = 0; + } + this.pumpTime = duration * 60; + } + + @Override + public byte[] getFirstData() { + aapsLogger.debug(LTag.PUMPCOMM, "step===" + step + "=====" + pumpTime); + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x04}; + byte[] data3 = Utils.intToBytes(step); + byte[] data4 = Utils.intToBytes(pumpTime); + byte[] data = Utils.concat(indexByte, data2, data3, data4); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x04, 0x01}; + byte[] data3 = Utils.intToBytes(0); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + int index = data[4]; + int status = data[6] & 0xff; + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + if (cancel) { + return EquilHistoryRecord.EventType.CANCEL_TEMPORARY_BASAL; + } else { + return EquilHistoryRecord.EventType.SET_TEMPORARY_BASAL; + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeGet.java new file mode 100644 index 00000000000..d78ebb977b2 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeGet.java @@ -0,0 +1,46 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; +import app.aaps.pump.equil.manager.Utils; + +public class CmdTimeGet extends BaseSetting { + public CmdTimeGet() { + super(System.currentTimeMillis()); + port = "0505"; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x00}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x00, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override + public void decodeConfirmData(byte[] data) { +// 67631679050017070e101319 + int index = data[4]; + int year = data[6] & 0xff; + int month = data[7] & 0xff; + int day = data[8] & 0xff; + int hour = data[9] & 0xff; + int min = data[10] & 0xff; + int s = data[11] & 0xff; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeSet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeSet.java new file mode 100644 index 00000000000..adb3f3edf5d --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTimeSet.java @@ -0,0 +1,58 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +import java.util.Calendar; + +public class CmdTimeSet extends BaseSetting { + public CmdTimeSet() { + super(System.currentTimeMillis()); + port = "0505"; + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x00}; + byte[] data3 = new byte[6]; + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR) - 2000; + int month = calendar.get(Calendar.MONTH) + 1; + int day = calendar.get(Calendar.DAY_OF_MONTH); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + int second = calendar.get(Calendar.SECOND); + + data3[0] = (byte) year; + data3[1] = (byte) month; + data3[2] = (byte) day; + data3[3] = (byte) hour; + data3[4] = (byte) minute; + data3[5] = (byte) second; + byte[] data = Utils.concat(indexByte, data2, data3); +// ZLog.e2("setTime==" + year + "==" + month + "===" + day + "===" + hour + "===" + minute + "===" + second); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x00, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + synchronized (this) { + setCmdStatus(true); + notify(); + } + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.SET_TIME; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTmepBasalGet.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTmepBasalGet.java new file mode 100644 index 00000000000..f12dcb1fae6 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdTmepBasalGet.java @@ -0,0 +1,38 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +public class CmdTmepBasalGet extends BaseSetting { + public CmdTmepBasalGet() { + super(System.currentTimeMillis()); + } + + @Override + public byte[] getFirstData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x02, 0x02}; + byte[] data3 = Utils.intToBytes(120); + byte[] data4 = Utils.intToBytes(120); + byte[] data = Utils.concat(indexByte, data2, data3, data4); + pumpReqIndex++; + return data; + } + + public byte[] getNextData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x02, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + public void decodeConfirmData(byte[] data) { + + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdUnPair.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdUnPair.java new file mode 100644 index 00000000000..6a27855d795 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/CmdUnPair.java @@ -0,0 +1,145 @@ +package app.aaps.pump.equil.manager.command; + + +import java.nio.ByteBuffer; +import java.security.MessageDigest; + +import app.aaps.core.interfaces.logging.LTag; +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.AESUtil; +import app.aaps.pump.equil.manager.EquilCmdModel; +import app.aaps.pump.equil.manager.EquilResponse; +import app.aaps.pump.equil.manager.Utils; + + +public class CmdUnPair extends BaseCmd { + String sn; + String password; + + public CmdUnPair(String name, String password) { + super(System.currentTimeMillis()); + port = "0E0E"; + this.password = password; + sn = name.replace("Equil - ", "").trim(); + sn = convertString(sn); + } + + byte[] randomPassword; + + public EquilResponse clear1() { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(Utils.hexStringToBytes(sn)); + byte[] pwd = messageDigest.digest(); + + byte[] equilPassword = AESUtil.getEquilPassWord(password); + randomPassword = Utils.generateRandomPassword(32); + byte[] data = Utils.concat(equilPassword, randomPassword); + EquilCmdModel equilCmdModel = AESUtil.aesEncrypt(pwd, data); + return responseCmd(equilCmdModel, "0D0D0000"); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + + } + + @Override + public EquilResponse getEquilResponse() { + response = new EquilResponse(createTime); + return clear1(); + } + + @Override public EquilResponse getNextEquilResponse() { + return getEquilResponse(); + } + + public EquilResponse decodeEquilPacket(byte[] data) { + if (!checkData(data)) { + return null; + } + byte code = data[4]; + int intValue = getIndex(code); + if (config) { + if (rspIndex == intValue) { + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decodeConfirm(); + isEnd = true; + response = new EquilResponse(createTime); + rspIndex = intValue; + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeEquilPacket error =====" + e.getMessage()); + + } + + return null; + } + boolean flag = isEnd(code); + ByteBuffer buffer = ByteBuffer.wrap(data); + response.add(buffer); + if (!flag) { + return null; + } + try { + EquilResponse list = decode(); + response = new EquilResponse(createTime); + config = true; + rspIndex = intValue; + return list; + } catch (Exception e) { + response = new EquilResponse(createTime); + aapsLogger.debug(LTag.PUMPCOMM, "decodeEquilPacket error=====" + e.getMessage()); + } + return null; + + } + + @Override + public EquilResponse decode() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + byte[] keyBytes = randomPassword; + + + byte[] data2 = new byte[]{ + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}; + String content = AESUtil.decrypt(equilCmdModel, keyBytes); + String pwd1 = content.substring(0, 64); + String pwd2 = content.substring(64); + runPwd = pwd2; + byte[] data1 = Utils.hexStringToBytes(pwd1); + byte[] data = Utils.concat(data1, data2); + + EquilCmdModel equilCmdModel2 = AESUtil.aesEncrypt(Utils.hexStringToBytes(runPwd), data); + runCode = equilCmdModel.getCode(); + return responseCmd(equilCmdModel2, port + runCode); + } + + + @Override + public EquilResponse decodeConfirm() throws Exception { + EquilCmdModel equilCmdModel = decodeModel(); + String content = AESUtil.decrypt(equilCmdModel, Utils.hexStringToBytes(runPwd)); + synchronized (this) { + setCmdStatus(true); + notify(); + } + return null; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return EquilHistoryRecord.EventType.UNPAIR_EQUIL; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/PumpEvent.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/PumpEvent.java new file mode 100644 index 00000000000..3b91b81c5e8 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/PumpEvent.java @@ -0,0 +1,105 @@ +package app.aaps.pump.equil.manager.command; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import app.aaps.core.interfaces.resources.ResourceHelper; +import app.aaps.pump.equil.R; + + +public class PumpEvent { + private int port; + private int type; + private int level;// + private String conent; + static List lists = new ArrayList<>(); + + public static void init(ResourceHelper rh) { + lists = new ArrayList<>(); + lists.add(new PumpEvent(4, 0, 0, "--")); + lists.add(new PumpEvent(4, 1, 1, rh.gs(R.string.equil_history_item1))); + lists.add(new PumpEvent(4, 1, 2, rh.gs(R.string.equil_history_item2))); + lists.add(new PumpEvent(4, 2, 2, rh.gs(R.string.equil_history_item3))); + lists.add(new PumpEvent(4, 3, 0, rh.gs(R.string.equil_history_item4))); + lists.add(new PumpEvent(4, 3, 2, rh.gs(R.string.equil_history_item5))); + lists.add(new PumpEvent(4, 5, 0, rh.gs(R.string.equil_history_item6))); + lists.add(new PumpEvent(4, 5, 1, rh.gs(R.string.equil_history_item7))); + lists.add(new PumpEvent(4, 6, 1, rh.gs(R.string.equil_history_item8))); + lists.add(new PumpEvent(4, 6, 2, rh.gs(R.string.equil_history_item9))); + lists.add(new PumpEvent(4, 7, 0, rh.gs(R.string.equil_history_item10))); + lists.add(new PumpEvent(4, 8, 0, rh.gs(R.string.equil_history_item11))); + lists.add(new PumpEvent(4, 9, 0, rh.gs(R.string.equil_history_item12))); + lists.add(new PumpEvent(4, 10, 0, rh.gs(R.string.equil_history_item13))); + lists.add(new PumpEvent(4, 11, 0, rh.gs(R.string.equil_history_item14))); + lists.add(new PumpEvent(5, 0, 1, rh.gs(R.string.equil_history_item15))); + lists.add(new PumpEvent(5, 0, 2, rh.gs(R.string.equil_history_item16))); + lists.add(new PumpEvent(5, 1, 0, rh.gs(R.string.equil_history_item17))); + lists.add(new PumpEvent(5, 1, 2, rh.gs(R.string.equil_history_item18))); + } + + public PumpEvent(int port, int type, int level, String conent) { + this.port = port; + this.type = type; + this.level = level; + this.conent = conent; + } + + + public static String getTips(int port, int type, int level) { + PumpEvent pumpEvent = new PumpEvent(port, type, level, ""); + int index = lists.indexOf(pumpEvent); + if (index == -1) { + return ""; + } + return lists.get(index).getConent(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PumpEvent pumpEvent = (PumpEvent) o; + return port == pumpEvent.port && type == pumpEvent.type && level == pumpEvent.level; + } + + @Override + public int hashCode() { + return Objects.hash(port, type, level); + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public String getConent() { + return conent; + } + + public void setConent(String conent) { + this.conent = conent; + } + + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/SettingCmd.java b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/SettingCmd.java new file mode 100644 index 00000000000..4e36030487e --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/manager/command/SettingCmd.java @@ -0,0 +1,54 @@ +package app.aaps.pump.equil.manager.command; + + +import app.aaps.pump.equil.database.EquilHistoryRecord; +import app.aaps.pump.equil.manager.Utils; + +public class SettingCmd extends BaseSetting { + public SettingCmd() { + super(System.currentTimeMillis()); + } + + + public byte[] getData() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] tzm = Utils.hexStringToBytes(getEquilDevices()); + byte[] data = Utils.concat(indexByte, tzm); + pumpReqIndex++; + return data; + } + + + public byte[] getData2() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x01, 0x07}; + byte[] data3 = Utils.intToBytes(120); + byte[] data = Utils.concat(indexByte, data2, data3); + pumpReqIndex++; + return data; + } + + @Override public byte[] getFirstData() { + return new byte[0]; + } + + @Override public byte[] getNextData() { + return new byte[0]; + } + + @Override public void decodeConfirmData(byte[] data) { + + } + + public byte[] getData3() { + byte[] indexByte = Utils.intToBytes(pumpReqIndex); + byte[] data2 = new byte[]{0x00, 0x07, 0x01}; + byte[] data = Utils.concat(indexByte, data2); + pumpReqIndex++; + return data; + } + + @Override public EquilHistoryRecord.EventType getEventType() { + return null; + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilHistoryRecordActivity.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilHistoryRecordActivity.kt new file mode 100644 index 00000000000..521606e023f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilHistoryRecordActivity.kt @@ -0,0 +1,482 @@ +package app.aaps.pump.equil.ui + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import app.aaps.core.interfaces.extensions.toVisibility +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.logging.LTag +import app.aaps.core.interfaces.plugin.ActivePlugin +import app.aaps.core.interfaces.pump.BlePreCheck +import app.aaps.core.interfaces.pump.defs.PumpType +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.AapsSchedulers +import app.aaps.core.interfaces.rx.bus.RxBus +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.activities.TranslatedDaggerAppCompatActivity +import app.aaps.pump.equil.EquilPumpPlugin +import app.aaps.pump.equil.R +import app.aaps.pump.equil.database.EquilHistoryPump +import app.aaps.pump.equil.database.EquilHistoryPumpDao +import app.aaps.pump.equil.database.EquilHistoryRecord +import app.aaps.pump.equil.database.EquilHistoryRecordDao +import app.aaps.pump.equil.database.ResolvedResult +import app.aaps.pump.equil.databinding.EquilHistoryRecordActivityBinding +import app.aaps.pump.equil.driver.definition.EquilHistoryEntryGroup +import app.aaps.pump.equil.events.EventEquilDataChanged +import app.aaps.pump.equil.manager.Utils +import app.aaps.pump.equil.manager.command.PumpEvent +import com.google.android.material.tabs.TabLayout +import info.nightscout.pump.common.utils.ProfileUtil +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale +import javax.inject.Inject +import kotlin.math.abs + +// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) +class EquilHistoryRecordActivity : TranslatedDaggerAppCompatActivity() { + + @Inject lateinit var sp: SP + @Inject lateinit var blePreCheck: BlePreCheck + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var context: Context + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var equilHistoryRecordDao: EquilHistoryRecordDao + @Inject lateinit var equilHistoryPumpDao: EquilHistoryPumpDao + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var equilPumpPlugin: EquilPumpPlugin + @Inject lateinit var rxBus: RxBus + @Inject lateinit var dateUtil: DateUtil + + private lateinit var binding: EquilHistoryRecordActivityBinding + private lateinit var llm: LinearLayoutManager + private lateinit var recyclerViewAdapter: RecyclerViewAdapter + + private val disposable = CompositeDisposable() + private var filteredHistoryList: MutableList = java.util.ArrayList() + private val fullHistoryList: MutableList = java.util.ArrayList() + private val calendar: Calendar = Calendar.getInstance() + + //private var typeListFull: List? = null + private var manualChange = false + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = EquilHistoryRecordActivityBinding.inflate(layoutInflater) + setContentView(binding.root) + title = rh.gs(R.string.equil_title_history_events) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + loadData() + + recyclerViewAdapter = RecyclerViewAdapter(filteredHistoryList, rh) + llm = LinearLayoutManager(this) + binding.recyclerview.run { + setHasFixedSize(true) + layoutManager = llm + adapter = recyclerViewAdapter + } + binding.recyclerviewEquil.run { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(this@EquilHistoryRecordActivity) + } + disposable += rxBus + .toObservable(EventEquilDataChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ + loadData() + loadDataEquil() + }, fabricPrivacy::logException) + //typeListFull = getTypeList(EquilHistoryEntry.getTranslatedList(rh)) + + val spinnerAdapter = ArrayAdapter(this, app.aaps.core.ui.R.layout.spinner_centered, EquilHistoryEntryGroup.getTranslatedList(rh)) + binding.equilHistorytype.run { + adapter = spinnerAdapter + onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + if (manualChange) return + val selected = selectedItem as EquilHistoryEntryGroup + filterHistory(selected) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + if (manualChange) return + filterHistory(EquilHistoryEntryGroup.All) + } + } + } + binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + processVisibility(tab.position) + } + + override fun onTabUnselected(tab: TabLayout.Tab) {} + override fun onTabReselected(tab: TabLayout.Tab) {} + }) + loadDataEquil() + } + + private fun processVisibility(position: Int) { + binding.recyclerview.visibility = (position == 0).toVisibility() + binding.recyclerviewEquil.visibility = (position == 1).toVisibility() + + } + + @SuppressLint("NotifyDataSetChanged") + private fun filterHistory(group: EquilHistoryEntryGroup) { + filteredHistoryList.clear() + aapsLogger.debug(LTag.PUMPCOMM, "Items on full list: {}", fullHistoryList.size) + if (group === EquilHistoryEntryGroup.All) { + aapsLogger.debug(LTag.PUMPCOMM, "all===") + filteredHistoryList.addAll(fullHistoryList) + } else { + filteredHistoryList.addAll(fullHistoryList.filter { it.type?.let { it1 -> groupForCommandType(it1) } == group }) + } + + recyclerViewAdapter.let { + it.historyList = filteredHistoryList + it.notifyDataSetChanged() + } + aapsLogger.debug(LTag.PUMPCOMM, "Items on filtered list: {}", filteredHistoryList.size) + } + + private fun groupForCommandType(type: EquilHistoryRecord.EventType): EquilHistoryEntryGroup { + return when (type) { + EquilHistoryRecord.EventType.INITIALIZE_EQUIL -> + EquilHistoryEntryGroup.Pair + + EquilHistoryRecord.EventType.INSERT_CANNULA -> + EquilHistoryEntryGroup.Pair + + EquilHistoryRecord.EventType.UNPAIR_EQUIL -> + EquilHistoryEntryGroup.Pair + // EquilHistoryRecord.EventType.DEACTIVATE_POD -> + // PumpHistoryEntryGroup.Prime + // + // EquilHistoryRecord.EventType.DISCARD_POD -> + // PumpHistoryEntryGroup.Prime + + EquilHistoryRecord.EventType.CANCEL_TEMPORARY_BASAL -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.CANCEL_EXTENDED_BOLUS -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.SET_EXTENDED_BOLUS -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.SET_BASAL_PROFILE -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.SET_TEMPORARY_BASAL -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.RESUME_DELIVERY -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.SUSPEND_DELIVERY -> + EquilHistoryEntryGroup.Basal + + EquilHistoryRecord.EventType.SET_BOLUS -> + EquilHistoryEntryGroup.Bolus + + EquilHistoryRecord.EventType.CANCEL_BOLUS -> + EquilHistoryEntryGroup.Bolus + + EquilHistoryRecord.EventType.SET_TIME -> + EquilHistoryEntryGroup.Configuration + + EquilHistoryRecord.EventType.SET_ALARM_MUTE -> + EquilHistoryEntryGroup.Configuration + + EquilHistoryRecord.EventType.SET_ALARM_SHAKE -> + EquilHistoryEntryGroup.Configuration + + EquilHistoryRecord.EventType.SET_ALARM_TONE -> + EquilHistoryEntryGroup.Configuration + + EquilHistoryRecord.EventType.SET_ALARM_TONE_AND_SHAK -> + EquilHistoryEntryGroup.Configuration + + // EquilHistoryRecord.EventType.READ_POD_PULSE_LOG -> + // PumpHistoryEntryGroup.Unknown + else -> { + EquilHistoryEntryGroup.All + + } + } + } + + private fun loadData() { + calendar.apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + add(Calendar.DAY_OF_MONTH, -5) + } + val startTime = calendar.timeInMillis + + aapsLogger.debug(LTag.PUMPCOMM, "loadData===" + dateUtil.dateAndTimeAndSecondsString(startTime) + "====") + disposable += equilHistoryRecordDao + .allSince(startTime, dateUtil.now()) + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribe({ historyList -> + aapsLogger.debug(LTag.PUMPCOMM, "historyList===" + historyList.size) + fullHistoryList.clear() + fullHistoryList.addAll(historyList) + }, fabricPrivacy::logException) + } + + private fun loadDataEquil() { + calendar.apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + add(Calendar.DAY_OF_MONTH, -5) + } + val startTime = calendar.timeInMillis + val endTime = dateUtil.now() + aapsLogger.debug(LTag.PUMPCOMM, "loadData===" + dateUtil.dateAndTimeAndSecondsString(startTime) + "====" + dateUtil.dateAndTimeAndSecondsString(endTime)) + disposable += equilHistoryPumpDao + .allFromByType(startTime, endTime, equilPumpPlugin.serialNumber()) + .subscribeOn(aapsSchedulers.io) + .observeOn(aapsSchedulers.main) + .subscribe({ historyList -> + aapsLogger.debug(LTag.PUMPCOMM, "loadDataEquil===" + historyList.size) + binding.recyclerviewEquil.swapAdapter(RecyclerViewAdapterEquil(toModels(historyList), rh), false) + }, fabricPrivacy::logException) + } + + class RecyclerViewAdapter internal constructor( + var historyList: List, + private val rh: ResourceHelper + ) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): HistoryViewHolder { + val v = LayoutInflater.from(viewGroup.context).inflate( + R.layout.equil_item_record, // + viewGroup, false + ) + return HistoryViewHolder(v) + } + + override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) { + val item = historyList[position] + holder.timeView.text = android.text.format.DateFormat.format( + "yyyy-MM-dd HH:mm:ss", + item.timestamp + ).toString() + // holder.timeView.text = item.type.name + // holder.typeView.text = item.serialNumber + holder.typeView.text = item.tempBasalRecord?.rate.toString() + holder.typeView.text = rh.gs(item.type!!.resourceId) + + if (!item.isSuccess()) { + holder.valueView.text = rh.gs(translatedFailure(item)) + return + } + holder.valueView.text = when (item.type) { + EquilHistoryRecord.EventType.SET_TEMPORARY_BASAL -> { + val tbr = item.tempBasalRecord + val duration = (tbr?.duration?.div(60 * 1000) ?: 0) + + tbr.let { + rh.gs(R.string.equil_common_history_tbr_value, it?.rate, duration) + } + } + + EquilHistoryRecord.EventType.SET_EXTENDED_BOLUS -> { + val tbr = item.tempBasalRecord + val duration = (tbr?.duration?.div(60 * 1000) ?: 0) + val rate = (tbr!!.rate * (60 / duration)) + tbr.let { + rh.gs(R.string.equil_common_history_tbr_value, rate, duration) + } + } + + EquilHistoryRecord.EventType.SET_BOLUS -> item.bolusRecord.let { rh.gs(R.string.equil_common_history_bolus_value, it?.amount) } + + EquilHistoryRecord.EventType.INSERT_CANNULA -> rh.gs(R.string.history_manual_confirm) + + EquilHistoryRecord.EventType.EQUIL_ALARM -> item.note + + EquilHistoryRecord.EventType.SET_BASAL_PROFILE -> ProfileUtil.getBasalProfilesDisplayable(item.basalValuesRecord!!.segments.toTypedArray(), PumpType.EQUIL) + + else -> rh.gs(R.string.equil_success) + } + } + + private fun translatedFailure(historyEntry: EquilHistoryRecord): Int = + when (historyEntry.resolvedStatus) { + ResolvedResult.NOT_FOUNT -> R.string.equil_command_not_found + ResolvedResult.CONNECT_ERROR -> R.string.equil_command_connect_error + ResolvedResult.FAILURE -> R.string.equil_command_connect_no_response + ResolvedResult.SUCCESS -> R.string.equil_success + ResolvedResult.NONE -> R.string.equil_none + else -> R.string.equil_command__unknown + } + + override fun getItemCount(): Int = historyList.size + + class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + var timeView: TextView + var typeView: TextView + var valueView: TextView + + // var valueView: TextView + init { + timeView = itemView.findViewById(R.id.equil_history_time) + typeView = itemView.findViewById(R.id.equil_history_source) + valueView = itemView.findViewById(R.id.equil_history_description) + } + } + + } + + private fun toModels(list: List): List { + val arrayList = ArrayList() + var record: EquilHistoryPump? = null + var record2: EquilHistoryPump? = null + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) + val list2 = list.sortedWith(compareBy(EquilHistoryPump::eventTimestamp, EquilHistoryPump::eventIndex)) + val iterator = list2.listIterator() + var pre: EquilHistoryPump? = null + + while (iterator.hasNext()) { + val next = iterator.next() + + // Process basal speed + if (record2 == null || record2.rate != next.rate) { + val format = dateFormat.format(next.eventTimestamp) + val valueOf = Utils.decodeSpeedToUH(next.rate).toString() + if (pre?.type == 10) { + // arrayList.add(ItemModel(format, valueOf, ItemModel.TYPE_BASAL, next.eventTimestamp)) + arrayList.add(ItemModel(format, valueOf, ItemModel.TYPE_BASAL_TEMP, next.eventTimestamp)) + } else { + arrayList.add(ItemModel(format, valueOf, ItemModel.TYPE_BASAL, next.eventTimestamp)) + + } + record2 = next + } + // Process bolus speed + if (record != null && next.largeRate != record.largeRate) { + val time = next.eventTimestamp + val time2 = record.eventTimestamp + + val format2 = dateFormat.format(time2) + val format3 = "%.3f".format( + (Math.abs(time - time2) / 1000.0) + * Utils.decodeSpeedToUS(record.largeRate) + ) + val t = (abs(time - time2) / 1000.0) + aapsLogger.debug(LTag.PUMPCOMM, "time===$t===$format3") + arrayList.add(ItemModel(format2, format3, ItemModel.TYPE_BOLUS, time2)) + record = null + } + pre = next + if (next.largeRate > 0) { + record = next + } + // Process event + val string = PumpEvent.getTips(next.port, next.type, next.level) + + if ("--" != string) { + val format4 = dateFormat.format(next.eventTimestamp) + arrayList.add(ItemModel(format4, string, ItemModel.TYPE_TEXT, next.eventTimestamp)) + } + } + + // Process remaining bolus speed + // record?.let { + // val decodeSpeedToUH = Utils.decodeSpeedToUH(it.largeRate) + // val format5 = format.format(it.eventTimestamp) + // arrayList.add(ItemModel(format5, "正在开始 $decodeSpeedToUH U/H", 4, it.eventTimestamp)) + // } + + return arrayList.reversed() + } + + data class ItemModel(val time: String, val text: String, val type: Int, var eventTime: Long) { + companion object { + + const val TYPE_BOLUS = 1 + const val TYPE_BASAL = 2 + const val TYPE_TEXT = 3 + const val TYPE_BOLUS_ING = 4 + const val TYPE_BASAL_TEMP = 5 + + } + } + + class RecyclerViewAdapterEquil internal constructor(private val historyList: List, private val rh: ResourceHelper) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): EquilHistoryViewHolder { + val v = LayoutInflater.from(viewGroup.context).inflate(R.layout.equil_item_record, viewGroup, false) + return EquilHistoryViewHolder(v) + } + + override fun onBindViewHolder(holder: EquilHistoryViewHolder, position: Int) { + val item = historyList[position] + Log.e(LTag.PUMPCOMM.tag, "onBindViewHolder $position") + holder.timeView.text = item.time + holder.typeView.text = item.text + val type = item.type + val textColor = when (type) { + ItemModel.TYPE_BOLUS -> rh.gc(R.color.equil_bolus) + ItemModel.TYPE_BASAL -> rh.gc(R.color.equil_basal) + ItemModel.TYPE_BASAL_TEMP -> rh.gc(R.color.equil_basal) + ItemModel.TYPE_BOLUS_ING -> rh.gc(R.color.equil_bolus_ing) + else -> rh.gc(R.color.equil_normal) + } + val text = when (type) { + ItemModel.TYPE_BOLUS -> rh.gs(R.string.equil_record_bolus, item.text) + ItemModel.TYPE_BASAL -> rh.gs(R.string.equil_record_basal, item.text) + ItemModel.TYPE_BASAL_TEMP -> rh.gs(R.string.equil_record_basal_temp, item.text) + else -> item.text + } + holder.timeView.text = item.time + holder.typeView.text = text + holder.typeView.setTextColor(textColor) + } + + override fun getItemCount(): Int = historyList.size + + class EquilHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + var timeView: TextView + var typeView: TextView + private var descriptionView: TextView + + init { + timeView = itemView.findViewById(R.id.equil_history_time) + typeView = itemView.findViewById(R.id.equil_history_source) + descriptionView = itemView.findViewById(R.id.equil_history_description) + descriptionView.visibility = View.GONE + + } + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairActivity.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairActivity.kt new file mode 100644 index 00000000000..8892be41b4f --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairActivity.kt @@ -0,0 +1,122 @@ +package app.aaps.pump.equil.ui + +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.os.SystemClock +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.plugin.ActivePlugin +import app.aaps.core.interfaces.profile.ProfileFunction +import app.aaps.core.interfaces.pump.BlePreCheck +import app.aaps.core.interfaces.queue.Callback +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.ui.activities.TranslatedDaggerAppCompatActivity +import app.aaps.pump.equil.EquilPumpPlugin +import app.aaps.pump.equil.R +import app.aaps.pump.equil.databinding.EquilUnpairActivityBinding +import app.aaps.pump.equil.events.EventEquilUnPairChanged +import app.aaps.pump.equil.manager.EquilManager +import app.aaps.pump.equil.manager.command.CmdUnPair +import app.aaps.pump.equil.ui.dlg.LoadingDlg +import javax.inject.Inject + +// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) +class EquilUnPairActivity : TranslatedDaggerAppCompatActivity() { + + @Inject lateinit var sp: SP + @Inject lateinit var blePreCheck: BlePreCheck + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var context: Context + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var equilPumpPlugin: EquilPumpPlugin + @Inject lateinit var rxBus: RxBus + @Inject lateinit var equilManager: EquilManager + + private lateinit var binding: EquilUnpairActivityBinding + @Inject lateinit var profileFunction: ProfileFunction + var errorCount = 0 + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = EquilUnpairActivityBinding.inflate(layoutInflater) + setContentView(binding.root) + title = rh.gs(R.string.equil_change) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + val name = equilManager.serialNumber + binding.tvHint2.text = String.format(rh.gs(R.string.equil_unpair), name) + binding.btnNext.setOnClickListener { + showUnPairConfig() + } + binding.btnDelete.visibility = View.GONE + binding.btnDelete.setOnClickListener { + rxBus.send(EventEquilUnPairChanged()) + equilPumpPlugin.clearData() + SystemClock.sleep(50) + finish() + } + } + + private fun showUnPairConfig() { + val name = equilManager.serialNumber + val content = String.format(rh.gs(R.string.equil_unpair_alert), name) + val alertDialog = AlertDialog.Builder(this) + .setTitle(rh.gs(R.string.equil_title_tips)) + .setMessage(content) + .setPositiveButton(rh.gs(app.aaps.core.ui.R.string.ok)) { _: DialogInterface, _: Int -> + unpair(name) + } + .setNegativeButton(rh.gs(app.aaps.core.ui.R.string.cancel)) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + } + .create() + alertDialog.show() + + } + + private fun unpair(name: String) { + showLoading() + commandQueue.customCommand(CmdUnPair(name, sp.getString(rh.gs(R.string.key_equil_pair_password), "")), object : Callback() { + override fun run() { + dismissLoading() + if (result.success) { + equilManager.serialNumber = "" + equilManager.address = "" + equilPumpPlugin.showToast(rh.gs(R.string.equil_unbind)) + rxBus.send(EventEquilUnPairChanged()) + equilPumpPlugin.clearData() + SystemClock.sleep(50) + finish() + } else { + errorCount += 1 + dismissLoading() + equilPumpPlugin.showToast(rh.gs(R.string.equil_error)) + if (errorCount > 5) { + runOnUiThread { + binding.btnDelete.visibility = View.VISIBLE + } + } + } + } + }) + } + + private fun showLoading() { + LoadingDlg().show(supportFragmentManager, "loading") + } + + private fun dismissLoading() { + val fragment = supportFragmentManager.findFragmentByTag("loading") + if (fragment is DialogFragment) { + fragment.dismiss() + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairDetachActivity.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairDetachActivity.kt new file mode 100644 index 00000000000..d171b02e33d --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/EquilUnPairDetachActivity.kt @@ -0,0 +1,124 @@ +package app.aaps.pump.equil.ui + +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.os.SystemClock +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.plugin.ActivePlugin +import app.aaps.core.interfaces.profile.ProfileFunction +import app.aaps.core.interfaces.pump.BlePreCheck +import app.aaps.core.interfaces.queue.Callback +import app.aaps.core.interfaces.queue.CommandQueue +import app.aaps.core.interfaces.resources.ResourceHelper +import app.aaps.core.interfaces.rx.AapsSchedulers +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.core.interfaces.sharedPreferences.SP +import app.aaps.core.interfaces.utils.fabric.FabricPrivacy +import app.aaps.core.ui.activities.TranslatedDaggerAppCompatActivity +import app.aaps.pump.equil.EquilPumpPlugin +import app.aaps.pump.equil.R +import app.aaps.pump.equil.data.RunMode +import app.aaps.pump.equil.databinding.EquilUnpairDetachActivityBinding +import app.aaps.pump.equil.driver.definition.ActivationProgress +import app.aaps.pump.equil.events.EventEquilUnPairChanged +import app.aaps.pump.equil.manager.EquilManager +import app.aaps.pump.equil.manager.command.CmdInsulinChange +import app.aaps.pump.equil.ui.dlg.LoadingDlg +import com.bumptech.glide.Glide +import io.reactivex.rxjava3.disposables.CompositeDisposable +import io.reactivex.rxjava3.kotlin.plusAssign +import javax.inject.Inject + +// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) +class EquilUnPairDetachActivity : TranslatedDaggerAppCompatActivity() { + + @Inject lateinit var sp: SP + @Inject lateinit var blePreCheck: BlePreCheck + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var context: Context + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var equilPumpPlugin: EquilPumpPlugin + @Inject lateinit var rxBus: RxBus + @Inject lateinit var aapsSchedulers: AapsSchedulers + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var equilManager: EquilManager + + private val disposable = CompositeDisposable() + + private lateinit var binding: EquilUnpairDetachActivityBinding + @Inject lateinit var profileFunction: ProfileFunction + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = EquilUnpairDetachActivityBinding.inflate(layoutInflater) + setContentView(binding.root) + title = rh.gs(R.string.equil_change) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + + disposable += rxBus + .toObservable(EventEquilUnPairChanged::class.java) + .observeOn(aapsSchedulers.main) + .subscribe({ finish() }, fabricPrivacy::logException) + Glide.with(this) + .asGif() + .load(R.drawable.equil_animation_wizard_detach) + .into(binding.imv) + binding.btnNext.setOnClickListener { + // startActivity(Intent(context, EquilPairInsertActivity::class.java)) + showUnPairConfig() + } + } + + private fun showUnPairConfig() { + + val alertDialog = AlertDialog.Builder(this) + .setTitle(rh.gs(R.string.equil_title_tips)) + .setMessage(rh.gs(R.string.equil_hint_dressing)) + .setPositiveButton(rh.gs(app.aaps.core.ui.R.string.ok)) { _: DialogInterface, _: Int -> + changeInsulin() + } + .setNegativeButton(rh.gs(app.aaps.core.ui.R.string.cancel)) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + } + .create() + alertDialog.show() + + } + + private fun changeInsulin() { + showLoading() + commandQueue.customCommand(CmdInsulinChange(), object : Callback() { + override fun run() { + if (result.success) { + equilManager.runMode = RunMode.STOP + equilPumpPlugin.resetData() + equilManager.activationProgress = ActivationProgress.CANNULA_CHANGE + dismissLoading() + startActivity(Intent(context, EquilUnPairActivity::class.java)) + } else { + equilPumpPlugin.showToast(rh.gs(R.string.equil_error)) + dismissLoading() + + } + } + }) + } + + private fun showLoading() { + LoadingDlg().show(supportFragmentManager, "loading") + } + + private fun dismissLoading() { + val fragment = supportFragmentManager.findFragmentByTag("loading") + if (fragment is DialogFragment) { + fragment.dismiss() + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilAutoDressingDlg.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilAutoDressingDlg.kt new file mode 100644 index 00000000000..2572d0d988c --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilAutoDressingDlg.kt @@ -0,0 +1,78 @@ +package app.aaps.pump.equil.ui.dlg + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.pump.equil.databinding.EquilAutoDressingDialogBinding +import dagger.android.support.DaggerDialogFragment +import javax.inject.Inject + +class EquilAutoDressingDlg : DaggerDialogFragment() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var rxBus: RxBus + + private var _binding: EquilAutoDressingDialogBinding? = null + + val binding get() = _binding!! + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + _binding = EquilAutoDressingDialogBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.btnCancel.setOnClickListener { + binding.btnCancel.let { onDialogResultListener?.invoke() } + + dismiss() + } + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + manager.beginTransaction().let { + it.add(this, tag) + it.commitAllowingStateLoss() + } + } catch (e: IllegalStateException) { + aapsLogger.debug(e.localizedMessage ?: e.toString()) + } + } + + fun setDialogResultListener(listener: () -> Unit) { + onDialogResultListener = listener + + } + + private var onDialogResultListener: (() -> Unit)? = null + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilChangeInsulinDlg.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilChangeInsulinDlg.kt new file mode 100644 index 00000000000..28356ff1005 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilChangeInsulinDlg.kt @@ -0,0 +1,74 @@ +package app.aaps.pump.equil.ui.dlg + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.pump.equil.R +import app.aaps.pump.equil.databinding.EquilDialogAlertDressingBinding +import com.bumptech.glide.Glide +import dagger.android.support.DaggerDialogFragment +import javax.inject.Inject + +class EquilChangeInsulinDlg : DaggerDialogFragment() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var rxBus: RxBus + + private var _binding: EquilDialogAlertDressingBinding? = null + + val binding get() = _binding!! + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + _binding = EquilDialogAlertDressingBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.btnCancel.setOnClickListener { dismiss() } + Glide.with(view) + .asGif() + .load(R.drawable.equil_animation_wizard_detach) + .into(binding.imv) + binding.btnOk.setOnClickListener { + dismiss() + } + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + manager.beginTransaction().let { + it.add(this, tag) + it.commitAllowingStateLoss() + } + } catch (e: IllegalStateException) { + aapsLogger.debug(e.localizedMessage ?: e.toString()) + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilPairConfigDlg.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilPairConfigDlg.kt new file mode 100644 index 00000000000..bc2f35e9c78 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilPairConfigDlg.kt @@ -0,0 +1,71 @@ +package app.aaps.pump.equil.ui.dlg + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.pump.equil.databinding.EquilPairConfigDialogBinding +import dagger.android.support.DaggerDialogFragment +import javax.inject.Inject + +class EquilPairConfigDlg : DaggerDialogFragment() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var rxBus: RxBus + + private var _binding: EquilPairConfigDialogBinding? = null + + val binding get() = _binding!! + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + _binding = EquilPairConfigDialogBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.btnSkip.setOnClickListener { + dismiss() + } + binding.btnNeed.setOnClickListener { + dismiss() + } + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + manager.beginTransaction().let { + it.add(this, tag) + it.commitAllowingStateLoss() + } + } catch (e: IllegalStateException) { + aapsLogger.debug(e.localizedMessage ?: e.toString()) + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilUnPairDlg.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilUnPairDlg.kt new file mode 100644 index 00000000000..5aa31241d87 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/EquilUnPairDlg.kt @@ -0,0 +1,69 @@ +package app.aaps.pump.equil.ui.dlg + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.pump.equil.databinding.EquilDialogAlertDressingBinding +import dagger.android.support.DaggerDialogFragment +import javax.inject.Inject + +class EquilUnPairDlg : DaggerDialogFragment() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var rxBus: RxBus + + private var _binding: EquilDialogAlertDressingBinding? = null + + val binding get() = _binding!! + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + _binding = EquilDialogAlertDressingBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + binding.btnCancel.setOnClickListener { dismiss() } + binding.btnOk.setOnClickListener { + dismiss() + } + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + manager.beginTransaction().let { + it.add(this, tag) + it.commitAllowingStateLoss() + } + } catch (e: IllegalStateException) { + aapsLogger.debug(e.localizedMessage ?: e.toString()) + } + } +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/LoadingDlg.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/LoadingDlg.kt new file mode 100644 index 00000000000..b61eaaa2026 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/dlg/LoadingDlg.kt @@ -0,0 +1,66 @@ +package app.aaps.pump.equil.ui.dlg + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import app.aaps.core.interfaces.logging.AAPSLogger +import app.aaps.core.interfaces.protection.ProtectionCheck +import app.aaps.core.interfaces.rx.bus.RxBus +import app.aaps.pump.equil.databinding.LoadingDialogBinding +import dagger.android.support.DaggerDialogFragment +import javax.inject.Inject + +class LoadingDlg : DaggerDialogFragment() { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var ctx: Context + @Inject lateinit var protectionCheck: ProtectionCheck + @Inject lateinit var rxBus: RxBus + + private var _binding: LoadingDialogBinding? = null + + val binding get() = _binding!! + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + _binding = LoadingDialogBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun show(manager: FragmentManager, tag: String?) { + try { + manager.beginTransaction().let { + it.add(this, tag) + it.commitAllowingStateLoss() + } + } catch (e: IllegalStateException) { + aapsLogger.debug(e.localizedMessage ?: e.toString()) + } + } + +} diff --git a/pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilChangeInsulinFragment.kt b/pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilChangeInsulinFragment.kt new file mode 100644 index 00000000000..55721bd89e8 --- /dev/null +++ b/pump/equil/src/main/java/app/aaps/pump/equil/ui/pair/EquilChangeInsulinFragment.kt @@ -0,0 +1,60 @@ +package app.aaps.pump.equil.ui.pair + +import android.os.Bundle +import android.view.View +import android.widget.Button +import androidx.navigation.fragment.findNavController +import app.aaps.core.interfaces.extensions.runOnUiThread +import app.aaps.core.interfaces.queue.Callback +import app.aaps.pump.equil.R +import app.aaps.pump.equil.data.RunMode +import app.aaps.pump.equil.manager.command.CmdInsulinChange +import com.bumptech.glide.Glide + +// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) +class EquilChangeInsulinFragment : EquilPairFragmentBase() { + + override fun getLayoutId(): Int { + return R.layout.equil_pair_change_insulin_fragment + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + Glide.with(view) + .asGif() + .load(R.drawable.equil_animation_wizard_detach) + .into(view.findViewById(R.id.imv)) + + view.findViewById