commit
ea5ec8e2c1
22 changed files with 749 additions and 420 deletions
|
@ -63,7 +63,7 @@ buildscript {
|
|||
|
||||
plugins {
|
||||
id "io.gitlab.arturbosch.detekt" version "1.16.0-RC2"
|
||||
id "org.jlleitschuh.gradle.ktlint" version "9.4.1"
|
||||
id "org.jlleitschuh.gradle.ktlint" version "10.1.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
|
@ -13,7 +13,11 @@
|
|||
<string name="key_omnipod_common_low_reservoir_alert_units" translatable="false">AAPS.Omnipod.low_reservoir_alert_units</string>
|
||||
<string name="key_omnipod_common_automatically_silence_alerts_enabled" translatable="false">AAPS.Omnipod.automatically_acknowledge_alerts_enabled</string>
|
||||
<string name="key_common_preferences_category_alerts_settings" translatable="false">common_preferences_category_alerts</string>
|
||||
|
||||
<string name="key_omnipod_common_preferences_category_notifications_settings"
|
||||
translatable="false">common_preferences_category_notifications_settings</string>
|
||||
<string name="key_omnipod_common_notification_uncertain_tbr_sound_enabled" translatable="false">AAPS.Omnipod.notification_uncertain_tbr_sound_enabled</string>
|
||||
<string name="key_omnipod_common_notification_uncertain_smb_sound_enabled" translatable="false">AAPS.Omnipod.notification_uncertain_smb_sound_enabled</string>
|
||||
<string name="key_omnipod_common_notification_uncertain_bolus_sound_enabled" translatable="false">AAPS.Omnipod.notification_uncertain_bolus_sound_enabled</string>
|
||||
<!-- Omnipod - Pod Management -->
|
||||
<string name="omnipod_common_pod_management_title">Pod Management</string>
|
||||
<string name="omnipod_common_pod_management_heading_actions">Actions</string>
|
||||
|
@ -132,6 +136,11 @@
|
|||
<string name="omnipod_common_preferences_category_other">Other</string>
|
||||
<string name="omnipod_common_preferences_category_alerts">Alerts</string>
|
||||
<string name="omnipod_common_preferences_category_confirmation_beeps">Confirmation Beeps</string>
|
||||
<string name="omnipod_common_preferences_category_notifications">Notifications</string>
|
||||
<string name="omnipod_common_preferences_notification_uncertain_tbr_sound_enabled">Sound for uncertain TBR notifications enabled</string>
|
||||
<string name="omnipod_common_preferences_notification_uncertain_smb_sound_enabled">Sound for
|
||||
uncertain SMB notifications enabled</string>
|
||||
<string name="omnipod_common_preferences_notification_uncertain_bolus_sound_enabled">Sound for uncertain bolus notifications enabled</string>
|
||||
|
||||
<!-- Omnipod - Pod Status -->
|
||||
<string name="omnipod_common_pod_status_no_active_pod">No Active Pod</string>
|
||||
|
|
|
@ -7,7 +7,9 @@ import dagger.android.HasAndroidInjector
|
|||
import info.nightscout.androidaps.activities.ErrorHelperActivity.Companion.runAlarm
|
||||
import info.nightscout.androidaps.data.DetailedBolusInfo
|
||||
import info.nightscout.androidaps.data.PumpEnactResult
|
||||
import info.nightscout.androidaps.events.EventPreferenceChange
|
||||
import info.nightscout.androidaps.events.EventProfileSwitchChanged
|
||||
import info.nightscout.androidaps.events.EventRefreshOverview
|
||||
import info.nightscout.androidaps.events.EventTempBasalChange
|
||||
import info.nightscout.androidaps.interfaces.*
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
|
@ -25,10 +27,7 @@ import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.common.definition.OmnipodCommandType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.common.queue.command.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ActivationProgress
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BeepType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.DeliveryStatus
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.PodConstants
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.ResponseType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.CommandConfirmed
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
|
@ -40,16 +39,24 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.ui.OmnipodDashOvervi
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.util.mapProfileToBasalProgram
|
||||
import info.nightscout.androidaps.queue.commands.Command
|
||||
import info.nightscout.androidaps.queue.commands.CustomCommand
|
||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||
import info.nightscout.androidaps.utils.T
|
||||
import info.nightscout.androidaps.utils.TimeChangeType
|
||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.rxkotlin.plusAssign
|
||||
import org.json.JSONObject
|
||||
import java.time.Duration
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.ceil
|
||||
import kotlin.random.Random
|
||||
|
||||
|
@ -63,6 +70,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
private val pumpSync: PumpSync,
|
||||
private val rxBus: RxBusWrapper,
|
||||
private val context: Context,
|
||||
private val aapsSchedulers: AapsSchedulers,
|
||||
private val fabricPrivacy: FabricPrivacy,
|
||||
|
||||
injector: HasAndroidInjector,
|
||||
aapsLogger: AAPSLogger,
|
||||
|
@ -70,9 +79,13 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
commandQueue: CommandQueueProvider
|
||||
) : PumpPluginBase(pluginDescription, injector, aapsLogger, resourceHelper, commandQueue), Pump {
|
||||
@Volatile var bolusCanceled = false
|
||||
@Volatile var bolusDeliveryInProgress = false
|
||||
|
||||
private val handler: Handler = Handler(Looper.getMainLooper())
|
||||
lateinit private var statusChecker: Runnable
|
||||
var nextPodWarningCheck : Long = 0
|
||||
private lateinit var statusChecker: Runnable
|
||||
var nextPodWarningCheck: Long = 0
|
||||
@Volatile var stopConnecting: CountDownLatch? = null
|
||||
private var disposables: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
companion object {
|
||||
private const val BOLUS_RETRY_INTERVAL_MS = 2000.toLong()
|
||||
|
@ -95,10 +108,32 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
statusChecker = Runnable {
|
||||
refreshStatusOnUnacknowledgedCommands()
|
||||
updatePodWarnings()
|
||||
// createFakeTBRWhenNoActivePod()
|
||||
// TODO: this is called from the main thread
|
||||
handler.postDelayed(statusChecker, STATUS_CHECK_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFakeTBRWhenNoActivePod() {
|
||||
if (!podStateManager.isPodRunning) {
|
||||
val expectedState = pumpSync.expectedPumpState()
|
||||
val tbr = expectedState.temporaryBasal
|
||||
if (tbr == null || tbr.rate != 0.0) {
|
||||
aapsLogger.info(LTag.PUMP, "createFakeTBRWhenNoActivePod")
|
||||
pumpSync.syncTemporaryBasalWithPumpId(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
rate = 0.0,
|
||||
duration = T.mins(PodConstants.MAX_POD_LIFETIME.toMinutes()).msecs(),
|
||||
isAbsolute = true,
|
||||
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
|
||||
pumpId = Random.Default.nextLong(), // we don't use this, just make sure it's unique
|
||||
pumpType = PumpType.OMNIPOD_DASH,
|
||||
pumpSerial = serialNumber()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePodWarnings() {
|
||||
if (System.currentTimeMillis() > nextPodWarningCheck) {
|
||||
if (!podStateManager.isPodRunning) {
|
||||
|
@ -121,7 +156,17 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
rxBus.send(EventNewNotification(notification))
|
||||
} else {
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED))
|
||||
// TODO: time out of sync notification?
|
||||
if (!podStateManager.sameTimeZone) {
|
||||
val notification =
|
||||
Notification(
|
||||
Notification.OMNIPOD_TIME_OUT_OF_SYNC,
|
||||
"Timezone on pod is different from the timezone on phone. " +
|
||||
"Basal rate is incorrect" +
|
||||
"Switch profile to fix",
|
||||
Notification.NORMAL
|
||||
)
|
||||
rxBus.send(EventNewNotification(notification))
|
||||
}
|
||||
}
|
||||
}
|
||||
nextPodWarningCheck = DateTimeUtil.getTimeInFutureFromMinutes(15)
|
||||
|
@ -132,13 +177,13 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
if (podStateManager.isPodRunning &&
|
||||
podStateManager.activeCommand != null &&
|
||||
commandQueue.size() == 0 &&
|
||||
commandQueue.performing() == null) {
|
||||
commandQueue.performing() == null
|
||||
) {
|
||||
commandQueue.readStatus("Unconfirmed command", null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInitialized(): Boolean {
|
||||
// TODO
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -153,37 +198,64 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
|
||||
override fun isConnected(): Boolean {
|
||||
|
||||
return true
|
||||
return !podStateManager.isPodRunning ||
|
||||
podStateManager.bluetoothConnectionState == OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTED
|
||||
}
|
||||
|
||||
override fun isConnecting(): Boolean {
|
||||
// TODO
|
||||
return false
|
||||
return stopConnecting != null
|
||||
}
|
||||
|
||||
override fun isHandshakeInProgress(): Boolean {
|
||||
// TODO
|
||||
return false
|
||||
return stopConnecting != null &&
|
||||
podStateManager.bluetoothConnectionState == OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTED
|
||||
}
|
||||
|
||||
override fun finishHandshaking() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
override fun connect(reason: String) {
|
||||
// empty on purpose
|
||||
aapsLogger.info(LTag.PUMP, "connect reason=$reason")
|
||||
podStateManager.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING
|
||||
|
||||
synchronized(this) {
|
||||
stopConnecting?.let {
|
||||
aapsLogger.warn(LTag.PUMP, "Already connecting: $stopConnecting")
|
||||
return
|
||||
}
|
||||
val stop = CountDownLatch(1)
|
||||
stopConnecting = stop
|
||||
}
|
||||
|
||||
thread(
|
||||
start = true,
|
||||
name = "ConnectionThread",
|
||||
) {
|
||||
try {
|
||||
stopConnecting?.let {
|
||||
val error = omnipodManager.connect(it).ignoreElements().blockingGet()
|
||||
aapsLogger.info(LTag.PUMPCOMM, "connect error=$error")
|
||||
}
|
||||
} finally {
|
||||
synchronized(this) {
|
||||
stopConnecting = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun disconnect(reason: String) {
|
||||
// TODO
|
||||
aapsLogger.info(LTag.PUMP, "disconnect reason=$reason")
|
||||
stopConnecting?.let { it.countDown() }
|
||||
omnipodManager.disconnect(false)
|
||||
}
|
||||
|
||||
override fun stopConnecting() {
|
||||
// TODO
|
||||
aapsLogger.info(LTag.PUMP, "stopConnecting")
|
||||
stopConnecting?.let { it.countDown() }
|
||||
omnipodManager.disconnect(true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun getPumpStatus(reason: String) {
|
||||
aapsLogger.debug(LTag.PUMP, "getPumpStatus reason=$reason")
|
||||
if (reason != "REQUESTED BY USER" && !podStateManager.isActivationCompleted) {
|
||||
|
@ -226,7 +298,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
pumpSync.syncTemporaryBasalWithPumpId(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
rate = 0.0,
|
||||
duration = T.mins(PodConstants.MAX_POD_LIFETIME.standardMinutes).msecs(),
|
||||
duration = T.mins(PodConstants.MAX_POD_LIFETIME.toMinutes()).msecs(),
|
||||
isAbsolute = true,
|
||||
type = PumpSync.TemporaryBasalType.PUMP_SUSPEND,
|
||||
pumpId = Random.Default.nextLong(), // we don't use this, just make sure it's unique
|
||||
|
@ -279,8 +351,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
podStateManager.createActiveCommand(historyId, basalProgram = basalProgram)
|
||||
},
|
||||
command = omnipodManager.setBasalProgram(basalProgram, hasBasalBeepEnabled()).ignoreElements(),
|
||||
post = failWhenUnconfirmed(deliverySuspended), // mark as failed even if it worked OK and try again vs. mark ok and
|
||||
// deny later
|
||||
post = failWhenUnconfirmed(deliverySuspended),
|
||||
// mark as failed even if it worked OK and try again vs. mark ok and deny later
|
||||
).toPumpEnactResult()
|
||||
}
|
||||
|
||||
|
@ -305,6 +377,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
}
|
||||
Completable.error(java.lang.IllegalStateException("Command not confirmed"))
|
||||
} else {
|
||||
showNotification(Notification.PROFILE_SET_OK, "Profile set OK", Notification.INFO, null)
|
||||
|
||||
Completable.complete()
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +394,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
.map {
|
||||
pumpSyncTempBasal(
|
||||
0.0,
|
||||
PodConstants.MAX_POD_LIFETIME.standardMinutes,
|
||||
PodConstants.MAX_POD_LIFETIME.toMinutes(),
|
||||
PumpSync.TemporaryBasalType.PUMP_SUSPEND
|
||||
)
|
||||
rxBus.send(EventTempBasalChange())
|
||||
|
@ -340,10 +414,38 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
super.onStart()
|
||||
podStateManager.onStart()
|
||||
handler.postDelayed(statusChecker, STATUS_CHECK_INTERVAL_MS)
|
||||
disposables += rxBus
|
||||
.toObservable(EventPreferenceChange::class.java)
|
||||
.observeOn(aapsSchedulers.main)
|
||||
.subscribe(
|
||||
{
|
||||
if (it.isChanged(
|
||||
resourceHelper,
|
||||
R.string.key_omnipod_common_expiration_reminder_enabled
|
||||
) ||
|
||||
it.isChanged(
|
||||
resourceHelper,
|
||||
R.string.key_omnipod_common_expiration_reminder_hours_before_shutdown
|
||||
) ||
|
||||
it.isChanged(
|
||||
resourceHelper,
|
||||
R.string.key_omnipod_common_low_reservoir_alert_enabled
|
||||
) ||
|
||||
it.isChanged(
|
||||
resourceHelper,
|
||||
R.string.key_omnipod_common_low_reservoir_alert_units
|
||||
)
|
||||
) {
|
||||
commandQueue.customCommand(CommandUpdateAlertConfiguration(), null)
|
||||
}
|
||||
},
|
||||
fabricPrivacy::logException
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
disposables.clear()
|
||||
handler.removeCallbacks(statusChecker)
|
||||
}
|
||||
|
||||
|
@ -363,7 +465,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
// TODO: what do we have to answer here if delivery is suspended?
|
||||
val running = podStateManager.basalProgram
|
||||
val equal = (mapProfileToBasalProgram(profile) == running)
|
||||
aapsLogger.info(LTag.PUMP, "isThisProfileSet: $equal")
|
||||
aapsLogger.info(LTag.PUMP, "set: $equal. profile=$profile, running=$running")
|
||||
return equal
|
||||
}
|
||||
|
||||
|
@ -400,7 +502,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
|
||||
override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult {
|
||||
try {
|
||||
aapsLogger.info(LTag.PUMP, "Delivering treatment: $detailedBolusInfo")
|
||||
bolusDeliveryInProgress = true
|
||||
aapsLogger.info(LTag.PUMP, "Delivering treatment: $detailedBolusInfo $bolusCanceled")
|
||||
val beepsConfigurationKey = if (detailedBolusInfo.bolusType == DetailedBolusInfo.BolusType.SMB)
|
||||
R.string.key_omnipod_common_smb_beeps_enabled
|
||||
else
|
||||
|
@ -469,9 +572,19 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
)
|
||||
} else {
|
||||
if (podStateManager.activeCommand != null) {
|
||||
val sound = if (sp.getBoolean(
|
||||
R.string
|
||||
.key_omnipod_common_notification_uncertain_bolus_sound_enabled,
|
||||
true
|
||||
)
|
||||
)
|
||||
R.raw.boluserror
|
||||
else
|
||||
0
|
||||
|
||||
showErrorDialog(
|
||||
"Bolus delivery status uncertain. Refresh pod status to confirm or deny.",
|
||||
R.raw.boluserror
|
||||
sound
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -487,6 +600,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
return ret
|
||||
} finally {
|
||||
bolusCanceled = false
|
||||
bolusDeliveryInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,7 +735,9 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
|
||||
override fun stopBolusDelivering() {
|
||||
aapsLogger.info(LTag.PUMP, "stopBolusDelivering called")
|
||||
bolusCanceled = true
|
||||
if (bolusDeliveryInProgress) {
|
||||
bolusCanceled = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun setTempBasalAbsolute(
|
||||
|
@ -910,7 +1026,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
.map {
|
||||
pumpSyncTempBasal(
|
||||
0.0,
|
||||
PodConstants.MAX_POD_LIFETIME.standardMinutes,
|
||||
PodConstants.MAX_POD_LIFETIME.toMinutes(),
|
||||
PumpSync.TemporaryBasalType.PUMP_SUSPEND
|
||||
)
|
||||
}
|
||||
|
@ -969,8 +1085,67 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateAlertConfiguration(): PumpEnactResult {
|
||||
// TODO
|
||||
return PumpEnactResult(injector).success(false).enacted(false).comment("NOT IMPLEMENTED")
|
||||
|
||||
val expirationReminderEnabled = sp.getBoolean(R.string.key_omnipod_common_expiration_reminder_enabled, true)
|
||||
val expirationHours = sp.getInt(R.string.key_omnipod_common_expiration_reminder_hours_before_shutdown, 7)
|
||||
val lowReservoirAlertEnabled = sp.getBoolean(R.string.key_omnipod_common_low_reservoir_alert_enabled, true)
|
||||
val lowReservoirAlertUnits = sp.getInt(R.string.key_omnipod_common_low_reservoir_alert_units, 10)
|
||||
|
||||
if (!podStateManager.differentAlertSettings(
|
||||
expirationReminderEnabled,
|
||||
expirationHours,
|
||||
lowReservoirAlertEnabled,
|
||||
lowReservoirAlertUnits
|
||||
)
|
||||
) {
|
||||
return PumpEnactResult(injector).success(true).enacted(false)
|
||||
}
|
||||
|
||||
val podLifeLeft = Duration.between(ZonedDateTime.now(), podStateManager.expiry)
|
||||
val expiryAlertDelay = podLifeLeft.minus(Duration.ofHours(expirationHours.toLong()))
|
||||
if (expiryAlertDelay.isNegative) {
|
||||
aapsLogger.warn(
|
||||
LTag.PUMPBTCOMM,
|
||||
"updateAlertConfiguration negative " +
|
||||
"expiryAlertDuration=$expiryAlertDelay"
|
||||
)
|
||||
PumpEnactResult(injector).success(false).enacted(false)
|
||||
}
|
||||
val alerts = listOf(
|
||||
AlertConfiguration(
|
||||
AlertType.LOW_RESERVOIR,
|
||||
enabled = lowReservoirAlertEnabled,
|
||||
durationInMinutes = 0,
|
||||
autoOff = false,
|
||||
AlertTrigger.ReservoirVolumeTrigger((lowReservoirAlertUnits * 10).toShort()),
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX
|
||||
),
|
||||
AlertConfiguration(
|
||||
AlertType.USER_SET_EXPIRATION,
|
||||
enabled = expirationReminderEnabled,
|
||||
durationInMinutes = 0,
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
expiryAlertDelay.toMinutes().toShort()
|
||||
),
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX2
|
||||
)
|
||||
)
|
||||
return executeProgrammingCommand(
|
||||
historyEntry = history.createRecord(OmnipodCommandType.CONFIGURE_ALERTS),
|
||||
command = omnipodManager.programAlerts(alerts).ignoreElements(),
|
||||
post = podStateManager.updateExpirationAlertSettings(
|
||||
expirationReminderEnabled,
|
||||
expirationHours
|
||||
).andThen(
|
||||
podStateManager.updateExpirationAlertSettings(
|
||||
lowReservoirAlertEnabled,
|
||||
lowReservoirAlertUnits
|
||||
)
|
||||
)
|
||||
).toPumpEnactResult()
|
||||
}
|
||||
|
||||
private fun playTestBeep(): PumpEnactResult {
|
||||
|
@ -1029,11 +1204,17 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
.map { handleCommandConfirmation(it) }
|
||||
.ignoreElement(),
|
||||
checkPodKaput(),
|
||||
refreshOverview(),
|
||||
post,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun refreshOverview(): Completable = Completable.defer {
|
||||
rxBus.send(EventRefreshOverview("Dash command", false))
|
||||
Completable.complete()
|
||||
}
|
||||
|
||||
private fun handleCommandConfirmation(confirmation: CommandConfirmed) {
|
||||
val command = confirmation.command
|
||||
val historyEntry = history.getById(command.historyId)
|
||||
|
@ -1065,6 +1246,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED))
|
||||
}
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS))
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC))
|
||||
}
|
||||
|
||||
OmnipodCommandType.SET_BASAL_PROFILE -> {
|
||||
|
@ -1086,6 +1268,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED))
|
||||
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS))
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1186,14 +1369,22 @@ class OmnipodDashPumpPlugin @Inject constructor(
|
|||
message,
|
||||
urgency
|
||||
)
|
||||
// TODO add back sound when we have options to disable it
|
||||
/*
|
||||
if (sound != null) {
|
||||
if (sound != null && soundEnabledForNotificationType(id)) {
|
||||
notification.soundId = sound
|
||||
}*/
|
||||
}
|
||||
rxBus.send(EventNewNotification(notification))
|
||||
}
|
||||
|
||||
private fun soundEnabledForNotificationType(notificationType: Int): Boolean {
|
||||
return when (notificationType) {
|
||||
Notification.OMNIPOD_TBR_ALERTS ->
|
||||
sp.getBoolean(R.string.key_omnipod_common_notification_uncertain_tbr_sound_enabled, true)
|
||||
Notification.OMNIPOD_UNCERTAIN_SMB ->
|
||||
sp.getBoolean(R.string.key_omnipod_common_notification_uncertain_smb_sound_enabled, true)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissNotification(id: Int) {
|
||||
rxBus.send(EventDismissNotification(id))
|
||||
}
|
||||
|
|
|
@ -9,12 +9,13 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.ResponseType
|
||||
import io.reactivex.Observable
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
interface OmnipodDashManager {
|
||||
|
||||
fun activatePodPart1(lowReservoirAlertTrigger: AlertTrigger.ReservoirVolumeTrigger?): Observable<PodEvent>
|
||||
|
||||
fun activatePodPart2(basalProgram: BasalProgram): Observable<PodEvent>
|
||||
fun activatePodPart2(basalProgram: BasalProgram, userConfiguredExpirationHours: Long?): Observable<PodEvent>
|
||||
|
||||
fun getStatus(type: ResponseType.StatusResponseType): Observable<PodEvent>
|
||||
|
||||
|
@ -39,4 +40,8 @@ interface OmnipodDashManager {
|
|||
fun silenceAlerts(alertTypes: EnumSet<AlertType>): Observable<PodEvent>
|
||||
|
||||
fun deactivatePod(): Observable<PodEvent>
|
||||
|
||||
fun disconnect(closeGatt: Boolean = false)
|
||||
|
||||
fun connect(stop: CountDownLatch): Observable<PodEvent>
|
||||
}
|
||||
|
|
|
@ -8,13 +8,17 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEven
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.GetVersionCommand.Companion.DEFAULT_UNIQUE_ID
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.PodConstants.Companion.MAX_POD_LIFETIME
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Action
|
||||
import io.reactivex.functions.Consumer
|
||||
import java.time.Duration
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -74,6 +78,22 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun disconnect(closeGatt: Boolean) {
|
||||
bleManager.disconnect(closeGatt)
|
||||
}
|
||||
|
||||
override fun connect(stop: CountDownLatch): Observable<PodEvent> {
|
||||
return observeConnectToPodWithStop(stop)
|
||||
.interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeConnectToPodWithStop(stop: CountDownLatch): Observable<PodEvent> {
|
||||
return Observable.defer {
|
||||
bleManager.connect(stop)
|
||||
.doOnError { throwable -> logger.warn(LTag.PUMPBTCOMM, "observeConnectToPodWithStop error=$throwable") }
|
||||
}
|
||||
}
|
||||
|
||||
private val observeConnectToPod: Observable<PodEvent>
|
||||
get() = Observable.defer {
|
||||
bleManager.connect()
|
||||
|
@ -221,10 +241,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observeConnectToPod,
|
||||
observeActivationPart1Commands(lowReservoirAlertTrigger)
|
||||
).doOnComplete(ActivationProgressUpdater(ActivationProgress.PHASE_1_COMPLETED))
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
.interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeActivationPart1Commands(lowReservoirAlertTrigger: AlertTrigger.ReservoirVolumeTrigger?): Observable<PodEvent> {
|
||||
|
@ -320,20 +337,19 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
return observables.reversed()
|
||||
}
|
||||
|
||||
override fun activatePodPart2(basalProgram: BasalProgram): Observable<PodEvent> {
|
||||
override fun activatePodPart2(basalProgram: BasalProgram, userConfiguredExpirationHours: Long?):
|
||||
Observable<PodEvent> {
|
||||
return Observable.concat(
|
||||
observePodReadyForActivationPart2,
|
||||
observeConnectToPod,
|
||||
observeActivationPart2Commands(basalProgram)
|
||||
observeActivationPart2Commands(basalProgram, userConfiguredExpirationHours)
|
||||
).doOnComplete(ActivationProgressUpdater(ActivationProgress.COMPLETED))
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
.interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeActivationPart2Commands(basalProgram: BasalProgram): Observable<PodEvent> {
|
||||
val observables = createActivationPart2Observables(basalProgram)
|
||||
private fun observeActivationPart2Commands(basalProgram: BasalProgram, userConfiguredExpirationHours: Long?):
|
||||
Observable<PodEvent> {
|
||||
val observables = createActivationPart2Observables(basalProgram, userConfiguredExpirationHours)
|
||||
|
||||
return if (observables.isEmpty()) {
|
||||
Observable.empty()
|
||||
|
@ -342,7 +358,11 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createActivationPart2Observables(basalProgram: BasalProgram): List<Observable<PodEvent>> {
|
||||
private fun createActivationPart2Observables(
|
||||
basalProgram: BasalProgram,
|
||||
userConfiguredExpirationHours: Long?
|
||||
):
|
||||
List<Observable<PodEvent>> {
|
||||
val observables = ArrayList<Observable<PodEvent>>()
|
||||
|
||||
if (podStateManager.activationProgress.isBefore(ActivationProgress.CANNULA_INSERTED)) {
|
||||
|
@ -368,33 +388,60 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
if (podStateManager.activationProgress.isBefore(ActivationProgress.UPDATED_EXPIRATION_ALERTS)) {
|
||||
val podLifeLeft = Duration.between(ZonedDateTime.now(), podStateManager.expiry)
|
||||
|
||||
val alerts = mutableListOf(
|
||||
AlertConfiguration(
|
||||
AlertType.EXPIRATION,
|
||||
enabled = true,
|
||||
durationInMinutes = TimeUnit.HOURS.toMinutes(7).toShort(),
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
TimeUnit.HOURS.toMinutes(72).toShort()
|
||||
), // FIXME use activation time
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX3
|
||||
),
|
||||
AlertConfiguration(
|
||||
AlertType.EXPIRATION_IMMINENT,
|
||||
enabled = true,
|
||||
durationInMinutes = 0,
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
TimeUnit.HOURS.toMinutes(79).toShort()
|
||||
), // FIXME use activation time
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX4
|
||||
)
|
||||
)
|
||||
val userExpiryAlertDelay = podLifeLeft.minus(
|
||||
Duration.ofHours(userConfiguredExpirationHours ?: MAX_POD_LIFETIME.toHours() + 1)
|
||||
)
|
||||
if (userExpiryAlertDelay.isNegative) {
|
||||
logger.warn(
|
||||
LTag.PUMPBTCOMM,
|
||||
"createActivationPart2Observables negative " +
|
||||
"expiryAlertDuration=$userExpiryAlertDelay"
|
||||
)
|
||||
} else {
|
||||
alerts.add(
|
||||
AlertConfiguration(
|
||||
AlertType.USER_SET_EXPIRATION,
|
||||
enabled = true,
|
||||
durationInMinutes = 0,
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
userExpiryAlertDelay.toMinutes().toShort()
|
||||
),
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX2
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
observables.add(
|
||||
observeSendProgramAlertsCommand(
|
||||
listOf(
|
||||
// FIXME use user configured expiration alert
|
||||
AlertConfiguration(
|
||||
AlertType.EXPIRATION,
|
||||
enabled = true,
|
||||
durationInMinutes = TimeUnit.HOURS.toMinutes(7).toShort(),
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
TimeUnit.HOURS.toMinutes(73).toShort()
|
||||
), // FIXME use activation time
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX3
|
||||
),
|
||||
AlertConfiguration(
|
||||
AlertType.EXPIRATION_IMMINENT,
|
||||
enabled = true,
|
||||
durationInMinutes = TimeUnit.HOURS.toMinutes(1).toShort(),
|
||||
autoOff = false,
|
||||
AlertTrigger.TimerTrigger(
|
||||
TimeUnit.HOURS.toMinutes(79).toShort()
|
||||
), // FIXME use activation time
|
||||
BeepType.FOUR_TIMES_BIP_BEEP,
|
||||
BeepRepetitionType.XXX4
|
||||
)
|
||||
),
|
||||
alerts,
|
||||
multiCommandFlag = true
|
||||
).doOnComplete(ActivationProgressUpdater(ActivationProgress.UPDATED_EXPIRATION_ALERTS))
|
||||
)
|
||||
|
@ -414,11 +461,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observeUniqueIdSet,
|
||||
observeConnectToPod,
|
||||
observeSendGetPodStatusCommand(type)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun setBasalProgram(basalProgram: BasalProgram, hasBasalBeepEnabled: Boolean): Observable<PodEvent> {
|
||||
|
@ -426,11 +469,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendProgramBasalCommand(basalProgram, hasBasalBeepEnabled)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeSendStopDeliveryCommand(
|
||||
|
@ -461,11 +500,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.ALL, hasBasalBeepEnabled)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun setTime(): Observable<PodEvent> {
|
||||
|
@ -495,11 +530,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendProgramTempBasalCommand(rate, durationInMinutes, tempBasalBeeps)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun stopTempBasal(hasTempBasalBeepEnabled: Boolean): Observable<PodEvent> {
|
||||
|
@ -507,11 +538,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.TEMP_BASAL, hasTempBasalBeepEnabled)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun bolus(units: Double, confirmationBeeps: Boolean, completionBeeps: Boolean): Observable<PodEvent> {
|
||||
|
@ -524,11 +551,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
confirmationBeeps,
|
||||
completionBeeps
|
||||
)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun stopBolus(beep: Boolean): Observable<PodEvent> {
|
||||
|
@ -536,11 +559,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.BOLUS, beep)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeSendConfigureBeepsCommand(
|
||||
|
@ -569,11 +588,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendConfigureBeepsCommand(immediateBeepType = beepType)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
override fun programAlerts(alertConfigurations: List<AlertConfiguration>): Observable<PodEvent> {
|
||||
|
@ -581,11 +596,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendProgramAlertsCommand(alertConfigurations)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
private fun observeSendSilenceAlertsCommand(alertTypes: EnumSet<AlertType>): Observable<PodEvent> {
|
||||
|
@ -607,11 +618,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
observePodRunning,
|
||||
observeConnectToPod,
|
||||
observeSendSilenceAlertsCommand(alertTypes)
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
}
|
||||
|
||||
private val observeSendDeactivateCommand: Observable<PodEvent>
|
||||
|
@ -630,11 +637,7 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
return Observable.concat(
|
||||
observeConnectToPod,
|
||||
observeSendDeactivateCommand
|
||||
)
|
||||
// TODO these would be common for any observable returned in a public function in this class
|
||||
.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
).interceptPodEvents()
|
||||
//
|
||||
.doOnComplete(podStateManager::reset)
|
||||
}
|
||||
|
@ -711,6 +714,12 @@ class OmnipodDashManagerImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Observable<PodEvent>.interceptPodEvents(): Observable<PodEvent> {
|
||||
return this.doOnNext(PodEventInterceptor())
|
||||
.doOnError(ErrorInterceptor())
|
||||
.subscribeOn(aapsSchedulers.io)
|
||||
}
|
||||
|
||||
inner class ErrorInterceptor : Consumer<Throwable> {
|
||||
|
||||
override fun accept(throwable: Throwable) {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Connection
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.ConnectionState
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface OmnipodDashBleManager {
|
||||
|
@ -13,9 +15,13 @@ interface OmnipodDashBleManager {
|
|||
|
||||
fun getStatus(): ConnectionState
|
||||
|
||||
fun connect(): Observable<PodEvent>
|
||||
// used for sync connections
|
||||
fun connect(timeoutMs: Long = Connection.BASE_CONNECT_TIMEOUT_MS * 3): Observable<PodEvent>
|
||||
|
||||
// used for async connections
|
||||
fun connect(stopConnectionLatch: CountDownLatch): Observable<PodEvent>
|
||||
|
||||
fun pairNewPod(): Observable<PodEvent>
|
||||
|
||||
fun disconnect()
|
||||
fun disconnect(closeGatt: Boolean = false)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.b
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -83,7 +84,7 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
}
|
||||
emitter.onComplete()
|
||||
} catch (ex: Exception) {
|
||||
disconnect()
|
||||
disconnect(false)
|
||||
emitter.tryOnError(ex)
|
||||
} finally {
|
||||
busy.set(false)
|
||||
|
@ -100,56 +101,54 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
return connection?.let { it.connectionState() }
|
||||
?: NotConnected
|
||||
}
|
||||
|
||||
override fun connect(): Observable<PodEvent> = Observable.create { emitter ->
|
||||
if (!busy.compareAndSet(false, true)) {
|
||||
throw BusyException()
|
||||
}
|
||||
try {
|
||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||
|
||||
val podAddress =
|
||||
podState.bluetoothAddress
|
||||
?: throw FailedToConnectException("Missing bluetoothAddress, activate the pod first")
|
||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||
val conn = connection
|
||||
?: Connection(podDevice, aapsLogger, context, podState)
|
||||
connection = conn
|
||||
if (conn.connectionState() is Connected && conn.session != null) {
|
||||
emitter.onNext(PodEvent.AlreadyConnected(podAddress))
|
||||
emitter.onComplete()
|
||||
return@create
|
||||
}
|
||||
|
||||
// two retries
|
||||
for (i in 1..MAX_NUMBER_OF_CONNECTION_ATTEMPTS) {
|
||||
try {
|
||||
// wait i * CONNECTION_TIMEOUT
|
||||
conn.connect(CONNECT_TIMEOUT_MULTIPLIER)
|
||||
break
|
||||
} catch (e: Exception) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "connect error=$e")
|
||||
if (i == MAX_NUMBER_OF_CONNECTION_ATTEMPTS) {
|
||||
emitter.onError(e)
|
||||
return@create
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||
emitter.onNext(PodEvent.EstablishingSession)
|
||||
establishSession(1.toByte())
|
||||
emitter.onNext(PodEvent.Connected)
|
||||
|
||||
emitter.onComplete()
|
||||
} catch (ex: Exception) {
|
||||
disconnect()
|
||||
emitter.tryOnError(ex)
|
||||
} finally {
|
||||
busy.set(false)
|
||||
}
|
||||
// used for sync connections
|
||||
override fun connect(timeoutMs: Long): Observable<PodEvent> {
|
||||
return connect(ConnectionWaitCondition(timeoutMs = timeoutMs))
|
||||
}
|
||||
|
||||
// used for async connections
|
||||
override fun connect(stopConnectionLatch: CountDownLatch): Observable<PodEvent> {
|
||||
return connect(ConnectionWaitCondition(stopConnection = stopConnectionLatch))
|
||||
}
|
||||
|
||||
private fun connect(connectionWaitCond: ConnectionWaitCondition): Observable<PodEvent> = Observable
|
||||
.create {
|
||||
emitter ->
|
||||
if (!busy.compareAndSet(false, true)) {
|
||||
throw BusyException()
|
||||
}
|
||||
try {
|
||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||
|
||||
val podAddress =
|
||||
podState.bluetoothAddress
|
||||
?: throw FailedToConnectException("Missing bluetoothAddress, activate the pod first")
|
||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||
val conn = connection
|
||||
?: Connection(podDevice, aapsLogger, context, podState)
|
||||
connection = conn
|
||||
if (conn.connectionState() is Connected && conn.session != null) {
|
||||
emitter.onNext(PodEvent.AlreadyConnected(podAddress))
|
||||
emitter.onComplete()
|
||||
return@create
|
||||
}
|
||||
|
||||
conn.connect(connectionWaitCond)
|
||||
|
||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||
emitter.onNext(PodEvent.EstablishingSession)
|
||||
establishSession(1.toByte())
|
||||
emitter.onNext(PodEvent.Connected)
|
||||
|
||||
emitter.onComplete()
|
||||
} catch (ex: Exception) {
|
||||
disconnect(false)
|
||||
emitter.tryOnError(ex)
|
||||
} finally {
|
||||
busy.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun establishSession(msgSeq: Byte) {
|
||||
val conn = assertConnected()
|
||||
|
||||
|
@ -187,7 +186,6 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
throw BusyException()
|
||||
}
|
||||
try {
|
||||
|
||||
if (podState.ltk != null) {
|
||||
emitter.onNext(PodEvent.AlreadyPaired)
|
||||
emitter.onComplete()
|
||||
|
@ -207,12 +205,14 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||
val conn = Connection(podDevice, aapsLogger, context, podState)
|
||||
connection = conn
|
||||
conn.connect(ConnectionWaitCondition(timeoutMs = 3 * Connection.BASE_CONNECT_TIMEOUT_MS))
|
||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||
|
||||
emitter.onNext(PodEvent.Pairing)
|
||||
val mIO = conn.msgIO ?: throw ConnectException("Connection lost")
|
||||
val ltkExchanger = LTKExchanger(
|
||||
aapsLogger,
|
||||
conn.msgIO,
|
||||
mIO,
|
||||
ids,
|
||||
)
|
||||
val pairResult = ltkExchanger.negotiateLTK()
|
||||
|
@ -221,27 +221,24 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
if (BuildConfig.DEBUG) {
|
||||
aapsLogger.info(LTag.PUMPCOMM, "Got LTK: ${pairResult.ltk.toHex()}")
|
||||
}
|
||||
|
||||
emitter.onNext(PodEvent.EstablishingSession)
|
||||
establishSession(pairResult.msgSeq)
|
||||
emitter.onNext(PodEvent.Connected)
|
||||
emitter.onComplete()
|
||||
} catch (ex: Exception) {
|
||||
disconnect()
|
||||
disconnect(false)
|
||||
emitter.tryOnError(ex)
|
||||
} finally {
|
||||
busy.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disconnect() {
|
||||
connection?.disconnect()
|
||||
override fun disconnect(closeGatt: Boolean) {
|
||||
connection?.disconnect(closeGatt)
|
||||
?: aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_NUMBER_OF_CONNECTION_ATTEMPTS = 2
|
||||
const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else.
|
||||
private const val CONNECT_TIMEOUT_MULTIPLIER = 4
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,26 +7,40 @@ import info.nightscout.androidaps.logging.LTag
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ConnectException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Connected
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Connection
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Connection.Companion.STOP_CONNECTING_CHECK_INTERVAL_MS
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.ConnectionWaitCondition
|
||||
import java.math.BigInteger
|
||||
import java.util.*
|
||||
|
||||
class ServiceDiscoverer(
|
||||
private val logger: AAPSLogger,
|
||||
private val gatt: BluetoothGatt,
|
||||
private val bleCallbacks: BleCommCallbacks
|
||||
private val bleCallbacks: BleCommCallbacks,
|
||||
private val connection: Connection
|
||||
) {
|
||||
|
||||
/***
|
||||
* This is first step after connection establishment
|
||||
*/
|
||||
fun discoverServices(): Map<CharacteristicType, BluetoothGattCharacteristic> {
|
||||
fun discoverServices(connectionWaitCond: ConnectionWaitCondition): Map<CharacteristicType, BluetoothGattCharacteristic> {
|
||||
logger.debug(LTag.PUMPBTCOMM, "Discovering services")
|
||||
bleCallbacks.startServiceDiscovery()
|
||||
val discover = gatt.discoverServices()
|
||||
if (!discover) {
|
||||
throw ConnectException("Could not start discovering services`")
|
||||
}
|
||||
bleCallbacks.waitForServiceDiscovery(DISCOVER_SERVICES_TIMEOUT_MS)
|
||||
connectionWaitCond.timeoutMs?.let {
|
||||
bleCallbacks.waitForServiceDiscovery(it)
|
||||
}
|
||||
connectionWaitCond.stopConnection?.let {
|
||||
while (!bleCallbacks.waitForServiceDiscovery(STOP_CONNECTING_CHECK_INTERVAL_MS)) {
|
||||
if (it.count == 0L || connection.connectionState() !is Connected) {
|
||||
throw ConnectException("stopConnecting called")
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug(LTag.PUMPBTCOMM, "Services discovered")
|
||||
val service = gatt.getService(SERVICE_UUID.toUuid())
|
||||
?: throw ConnectException("Service not found: $SERVICE_UUID")
|
||||
|
@ -34,11 +48,10 @@ class ServiceDiscoverer(
|
|||
?: throw ConnectException("Characteristic not found: ${CharacteristicType.CMD.value}")
|
||||
val dataChar = service.getCharacteristic(CharacteristicType.DATA.uuid)
|
||||
?: throw ConnectException("Characteristic not found: ${CharacteristicType.DATA.value}")
|
||||
var chars = mapOf(
|
||||
return mapOf(
|
||||
CharacteristicType.CMD to cmdChar,
|
||||
CharacteristicType.DATA to dataChar
|
||||
)
|
||||
return chars
|
||||
}
|
||||
|
||||
private fun String.toUuid(): UUID = UUID(
|
||||
|
@ -49,6 +62,5 @@ class ServiceDiscoverer(
|
|||
companion object {
|
||||
|
||||
private const val SERVICE_UUID = "1a7e-4024-e3ed-4464-8b7e-751e03d0dc5f"
|
||||
private const val DISCOVER_SERVICES_TIMEOUT_MS = 5000
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ class BleCommCallbacks(
|
|||
if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
|
||||
connected.countDown()
|
||||
}
|
||||
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
||||
if (newState == BluetoothProfile.STATE_DISCONNECTED && status != BluetoothGatt.GATT_SUCCESS) {
|
||||
// If status == SUCCESS, it means that we initiated the disconnect.
|
||||
disconnectHandler.onConnectionLost(status)
|
||||
}
|
||||
}
|
||||
|
@ -53,24 +54,28 @@ class BleCommCallbacks(
|
|||
}
|
||||
}
|
||||
|
||||
fun waitForConnection(timeoutMs: Int) {
|
||||
fun waitForConnection(timeoutMs: Long): Boolean {
|
||||
val latch = connected
|
||||
try {
|
||||
connected.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
||||
latch.await(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while waiting for Connection")
|
||||
}
|
||||
return latch.count == 0L
|
||||
}
|
||||
|
||||
fun startServiceDiscovery() {
|
||||
serviceDiscoveryComplete = CountDownLatch(1)
|
||||
}
|
||||
|
||||
fun waitForServiceDiscovery(timeoutMs: Int) {
|
||||
fun waitForServiceDiscovery(timeoutMs: Long): Boolean {
|
||||
val latch = serviceDiscoveryComplete
|
||||
try {
|
||||
serviceDiscoveryComplete.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
||||
latch.await(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
} catch (e: InterruptedException) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while waiting for ServiceDiscovery")
|
||||
}
|
||||
return latch.count == 0L
|
||||
}
|
||||
|
||||
fun confirmWrite(expectedPayload: ByteArray, expectedUUID: String, timeoutMs: Long): WriteConfirmation {
|
||||
|
@ -206,6 +211,8 @@ class BleCommCallbacks(
|
|||
|
||||
fun resetConnection() {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Reset connection")
|
||||
connected?.countDown()
|
||||
serviceDiscoveryComplete?.countDown()
|
||||
connected = CountDownLatch(1)
|
||||
serviceDiscoveryComplete = CountDownLatch(1)
|
||||
flushConfirmationQueue()
|
||||
|
|
|
@ -11,6 +11,7 @@ import info.nightscout.androidaps.logging.LTag
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.WriteConfirmationError
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.WriteConfirmationSuccess
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandRTS
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -84,12 +85,17 @@ open class BleIO(
|
|||
* Called before sending a new message.
|
||||
* The incoming queues should be empty, so we log when they are not.
|
||||
*/
|
||||
fun flushIncomingQueue() {
|
||||
open fun flushIncomingQueue(): Boolean {
|
||||
var foundRTS = false
|
||||
do {
|
||||
val found = incomingPackets.poll()?.also {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: queue not empty, flushing: {${it.toHex()}")
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: queue not empty, flushing: ${it.toHex()}")
|
||||
if (it.isNotEmpty() && it[0] == BleCommandRTS.data[0]) {
|
||||
foundRTS = true
|
||||
}
|
||||
}
|
||||
} while (found != null)
|
||||
return foundRTS
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,7 +35,12 @@ class MessageIO(
|
|||
|
||||
@Suppress("ReturnCount")
|
||||
fun sendMessage(msg: MessagePacket): MessageSendResult {
|
||||
cmdBleIO.flushIncomingQueue()
|
||||
val foundRTS = cmdBleIO.flushIncomingQueue()
|
||||
if (foundRTS) {
|
||||
val msg = receiveMessage(false)
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "sendMessage received message=$msg")
|
||||
throw IllegalStateException("Received message while trying to send")
|
||||
}
|
||||
dataBleIO.flushIncomingQueue()
|
||||
|
||||
val rtsSendResult = cmdBleIO.sendAndConfirmPacket(BleCommandRTS.data)
|
||||
|
@ -85,11 +90,13 @@ class MessageIO(
|
|||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
fun receiveMessage(): MessagePacket? {
|
||||
val expectRTS = cmdBleIO.expectCommandType(BleCommandRTS, MESSAGE_READ_TIMEOUT_MS)
|
||||
if (expectRTS !is BleConfirmSuccess) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Error reading RTS: $expectRTS")
|
||||
return null
|
||||
fun receiveMessage(readRTS: Boolean = true): MessagePacket? {
|
||||
if (readRTS) {
|
||||
val expectRTS = cmdBleIO.expectCommandType(BleCommandRTS, MESSAGE_READ_TIMEOUT_MS)
|
||||
if (expectRTS !is BleConfirmSuccess) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Error reading RTS: $expectRTS")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val sendResult = cmdBleIO.sendAndConfirmPacket(BleCommandCTS.data)
|
||||
|
@ -219,6 +226,6 @@ class MessageIO(
|
|||
companion object {
|
||||
|
||||
private const val MAX_PACKET_READ_TRIES = 4
|
||||
private const val MESSAGE_READ_TIMEOUT_MS = 2500.toLong()
|
||||
private const val MESSAGE_READ_TIMEOUT_MS = 5000.toLong()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.bluetooth.BluetoothGatt
|
|||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import info.nightscout.androidaps.extensions.toHex
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
|
@ -13,105 +14,100 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Ids
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ServiceDiscoverer
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ConnectException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleSendSuccess
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.DataBleIO
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.IncomingPackets
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
sealed class ConnectionState
|
||||
|
||||
object Connecting : ConnectionState()
|
||||
object Connected : ConnectionState()
|
||||
object NotConnected : ConnectionState()
|
||||
|
||||
data class ConnectionWaitCondition(var timeoutMs: Long? = null, val stopConnection: CountDownLatch? = null) {
|
||||
init {
|
||||
if (timeoutMs == null && stopConnection == null) {
|
||||
throw IllegalArgumentException("One of timeoutMs or stopConnection has to be non null")
|
||||
}
|
||||
if (timeoutMs != null && stopConnection != null) {
|
||||
throw IllegalArgumentException("One of timeoutMs or stopConnection has to be null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Connection(
|
||||
private val podDevice: BluetoothDevice,
|
||||
private val aapsLogger: AAPSLogger,
|
||||
context: Context,
|
||||
private val context: Context,
|
||||
private val podState: OmnipodDashPodStateManager
|
||||
) : DisconnectHandler {
|
||||
|
||||
private val incomingPackets = IncomingPackets()
|
||||
private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets, this)
|
||||
private val gattConnection: BluetoothGatt
|
||||
private var gattConnection: BluetoothGatt? = null
|
||||
|
||||
private val bluetoothManager: BluetoothManager =
|
||||
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
|
||||
// The session is Synchronized because we can lose the connection right when establishing it
|
||||
@Volatile
|
||||
var session: Session? = null
|
||||
@Synchronized get
|
||||
@Synchronized set
|
||||
private val cmdBleIO: CmdBleIO
|
||||
private val dataBleIO: DataBleIO
|
||||
|
||||
init {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}")
|
||||
@Volatile
|
||||
var msgIO: MessageIO? = null
|
||||
|
||||
fun connect(connectionWaitCond: ConnectionWaitCondition) {
|
||||
aapsLogger.debug("Connecting connectionWaitCond=$connectionWaitCond")
|
||||
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING
|
||||
val autoConnect = false
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING
|
||||
gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
||||
// OnDisconnect can be called after this point!!!
|
||||
val state = waitForConnection(2)
|
||||
if (state !is Connected) {
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED
|
||||
throw FailedToConnectException(podDevice.address)
|
||||
}
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTED
|
||||
val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
|
||||
val discoveredCharacteristics = discoverer.discoverServices()
|
||||
cmdBleIO = CmdBleIO(
|
||||
aapsLogger,
|
||||
discoveredCharacteristics[CharacteristicType.CMD]!!,
|
||||
incomingPackets
|
||||
.cmdQueue,
|
||||
gattConnection,
|
||||
bleCommCallbacks
|
||||
)
|
||||
dataBleIO = DataBleIO(
|
||||
aapsLogger,
|
||||
discoveredCharacteristics[CharacteristicType.DATA]!!,
|
||||
incomingPackets
|
||||
.dataQueue,
|
||||
gattConnection,
|
||||
bleCommCallbacks
|
||||
)
|
||||
|
||||
val sendResult = cmdBleIO.hello()
|
||||
if (sendResult !is BleSendSuccess) {
|
||||
throw FailedToConnectException("Could not send HELLO command to ${podDevice.address}")
|
||||
}
|
||||
cmdBleIO.readyToRead()
|
||||
dataBleIO.readyToRead()
|
||||
}
|
||||
|
||||
val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
|
||||
|
||||
fun connect(timeoutMultiplier: Int) {
|
||||
if (session != null) {
|
||||
disconnect()
|
||||
}
|
||||
aapsLogger.debug("Connecting")
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTING
|
||||
|
||||
if (!gattConnection.connect()) {
|
||||
val gatt = gattConnection
|
||||
?: podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
||||
gattConnection = gatt
|
||||
if (!gatt.connect()) {
|
||||
throw FailedToConnectException("connect() returned false")
|
||||
}
|
||||
|
||||
if (waitForConnection(timeoutMultiplier) !is Connected) {
|
||||
val before = SystemClock.elapsedRealtime()
|
||||
if (waitForConnection(connectionWaitCond) !is Connected) {
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED
|
||||
throw FailedToConnectException(podDevice.address)
|
||||
}
|
||||
val waitedMs = SystemClock.elapsedRealtime() - before
|
||||
val timeoutMs = connectionWaitCond.timeoutMs
|
||||
if (timeoutMs != null) {
|
||||
var newTimeout = timeoutMs - waitedMs
|
||||
if (newTimeout < MIN_DISCOVERY_TIMEOUT_MS) {
|
||||
newTimeout = MIN_DISCOVERY_TIMEOUT_MS
|
||||
}
|
||||
connectionWaitCond.timeoutMs = newTimeout
|
||||
}
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.CONNECTED
|
||||
|
||||
val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
|
||||
val discovered = discoverer.discoverServices()
|
||||
dataBleIO.characteristic = discovered[CharacteristicType.DATA]!!
|
||||
cmdBleIO.characteristic = discovered[CharacteristicType.CMD]!!
|
||||
|
||||
val discoverer = ServiceDiscoverer(aapsLogger, gatt, bleCommCallbacks, this)
|
||||
val discovered = discoverer.discoverServices(connectionWaitCond)
|
||||
val cmdBleIO = CmdBleIO(
|
||||
aapsLogger,
|
||||
discovered[CharacteristicType.CMD]!!,
|
||||
incomingPackets
|
||||
.cmdQueue,
|
||||
gatt,
|
||||
bleCommCallbacks
|
||||
)
|
||||
val dataBleIO = DataBleIO(
|
||||
aapsLogger,
|
||||
discovered[CharacteristicType.DATA]!!,
|
||||
incomingPackets
|
||||
.dataQueue,
|
||||
gatt,
|
||||
bleCommCallbacks
|
||||
)
|
||||
msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
|
||||
// val ret = gattConnection.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
|
||||
// aapsLogger.info(LTag.PUMPBTCOMM, "requestConnectionPriority: $ret")
|
||||
cmdBleIO.hello()
|
||||
|
@ -119,18 +115,33 @@ class Connection(
|
|||
dataBleIO.readyToRead()
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting")
|
||||
fun disconnect(closeGatt: Boolean) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting closeGatt=$closeGatt")
|
||||
podState.bluetoothConnectionState = OmnipodDashPodStateManager.BluetoothConnectionState.DISCONNECTED
|
||||
|
||||
gattConnection.disconnect()
|
||||
if (closeGatt) {
|
||||
gattConnection?.close()
|
||||
gattConnection = null
|
||||
} else {
|
||||
gattConnection?.disconnect()
|
||||
}
|
||||
bleCommCallbacks.resetConnection()
|
||||
session = null
|
||||
msgIO = null
|
||||
}
|
||||
|
||||
private fun waitForConnection(timeoutMultiplier: Int): ConnectionState {
|
||||
private fun waitForConnection(connectionWaitCond: ConnectionWaitCondition): ConnectionState {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "waitForConnection connectionWaitCond=$connectionWaitCond")
|
||||
try {
|
||||
bleCommCallbacks.waitForConnection(BASE_CONNECT_TIMEOUT_MS * timeoutMultiplier)
|
||||
connectionWaitCond.timeoutMs?.let {
|
||||
bleCommCallbacks.waitForConnection(it)
|
||||
}
|
||||
connectionWaitCond.stopConnection?.let {
|
||||
while (!bleCommCallbacks.waitForConnection(STOP_CONNECTING_CHECK_INTERVAL_MS)) {
|
||||
if (it.count == 0L) {
|
||||
throw ConnectException("stopConnecting called")
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
// We are still going to check if connection was successful
|
||||
aapsLogger.info(LTag.PUMPBTCOMM, "Interrupted while waiting for connection")
|
||||
|
@ -148,7 +159,9 @@ class Connection(
|
|||
}
|
||||
|
||||
fun establishSession(ltk: ByteArray, msgSeq: Byte, ids: Ids, eapSqn: ByteArray): EapSqn? {
|
||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, ids, msgSeq)
|
||||
val mIO = msgIO ?: throw ConnectException("Connection lost")
|
||||
|
||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, mIO, ltk, eapSqn, ids, msgSeq)
|
||||
return when (val keys = eapAkaExchanger.negotiateSessionKeys()) {
|
||||
is SessionNegotiationResynchronization -> {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
@ -168,7 +181,7 @@ class Connection(
|
|||
keys.nonce,
|
||||
keys.ck
|
||||
)
|
||||
session = Session(aapsLogger, msgIO, ids, sessionKeys = keys, enDecrypt = enDecrypt)
|
||||
session = Session(aapsLogger, mIO, ids, sessionKeys = keys, enDecrypt = enDecrypt)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -177,10 +190,12 @@ class Connection(
|
|||
// This will be called from a different thread !!!
|
||||
override fun onConnectionLost(status: Int) {
|
||||
aapsLogger.info(LTag.PUMPBTCOMM, "Lost connection with status: $status")
|
||||
disconnect()
|
||||
disconnect(false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BASE_CONNECT_TIMEOUT_MS = 10000
|
||||
const val BASE_CONNECT_TIMEOUT_MS = 10000L
|
||||
const val MIN_DISCOVERY_TIMEOUT_MS = 10000L
|
||||
const val STOP_CONNECTING_CHECK_INTERVAL_MS = 500L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||
|
||||
import info.nightscout.androidaps.extensions.toHex
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||
import java.util.*
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed class CommandSendResult
|
||||
object CommandSendSuccess : CommandSendResult()
|
||||
|
@ -113,10 +112,10 @@ class Session(
|
|||
|
||||
// TODO verify length
|
||||
|
||||
//val uniqueId = data.copyOfRange(0, 4)
|
||||
//val lenghtAndSequenceNumber = data.copyOfRange(4, 6)
|
||||
// val uniqueId = data.copyOfRange(0, 4)
|
||||
// val lenghtAndSequenceNumber = data.copyOfRange(4, 6)
|
||||
val payload = data.copyOfRange(6, data.size - 2)
|
||||
//val crc = data.copyOfRange(data.size - 2, data.size)
|
||||
// val crc = data.copyOfRange(data.size - 2, data.size)
|
||||
|
||||
// TODO validate uniqueId, sequenceNumber and crc
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ enum class BeepRepetitionType(
|
|||
val value: Byte
|
||||
) {
|
||||
|
||||
XXX(0x01.toByte()), // Used in lump of coal alert
|
||||
XXX2(0x03.toByte()), // Used in low reservoir alert
|
||||
XXX3(0x05.toByte()), // Used in user pod expiration alert
|
||||
XXX4(0x06.toByte()), // Used in pod expiration alert
|
||||
XXX5(0x08.toByte()); // Used in imminent pod expiration alert
|
||||
XXX(0x01.toByte()), // Used in lump of coal alert, LOW_RESERVOIR
|
||||
XXX2(0x03.toByte()), // Used in USER_SET_EXPIRATION
|
||||
XXX3(0x05.toByte()), // published system expiration alert
|
||||
XXX4(0x06.toByte()), // Used in imminent pod expiration alert
|
||||
XXX5(0x08.toByte()); // Lump of coal alert
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition
|
||||
|
||||
import org.joda.time.Duration
|
||||
import java.time.Duration
|
||||
|
||||
class PodConstants {
|
||||
companion object {
|
||||
val MAX_POD_LIFETIME = Duration.standardHours(80)
|
||||
val MAX_POD_LIFETIME = Duration.ofMinutes(80)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state
|
|||
import info.nightscout.androidaps.data.DetailedBolusInfo
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
|
||||
|
@ -11,12 +10,9 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.VersionResponse
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.Duration
|
||||
import java.io.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
|
||||
sealed class CommandConfirmationFromState
|
||||
|
@ -37,11 +33,12 @@ interface OmnipodDashPodStateManager {
|
|||
var bluetoothConnectionState: BluetoothConnectionState
|
||||
|
||||
var timeZone: TimeZone
|
||||
val sameTimeZone: Boolean // The TimeZone is the same on the phone and on the pod
|
||||
val lastUpdatedSystem: Long // System.currentTimeMillis()
|
||||
val lastStatusResponseReceived: Long
|
||||
val time: DateTime?
|
||||
val timeDrift: Duration?
|
||||
val expiry: DateTime?
|
||||
val time: ZonedDateTime?
|
||||
val timeDrift: java.time.Duration?
|
||||
val expiry: ZonedDateTime?
|
||||
|
||||
val messageSequenceNumber: Short
|
||||
val sequenceNumberOfLastProgrammingCommand: Short?
|
||||
|
@ -104,6 +101,9 @@ interface OmnipodDashPodStateManager {
|
|||
- after getPodStatus was successful(we have an up-to-date podStatus)
|
||||
*/
|
||||
fun recoverActivationFromPodStatus(): String?
|
||||
fun differentAlertSettings(expirationReminderEnabled: Boolean, expirationHours: Int, lowReservoirAlertEnabled: Boolean, lowReservoirAlertUnits: Int): Boolean
|
||||
fun updateExpirationAlertSettings(expirationReminderEnabled: Boolean, expirationHours: Int): Completable
|
||||
fun updateLowReservoirAlertSettings(lowReservoirAlertEnabled: Boolean, lowReservoirAlertUnits: Int): Completable
|
||||
|
||||
data class ActiveCommand(
|
||||
val sequence: Short,
|
||||
|
|
|
@ -11,7 +11,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.PairResult
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.EapSqn
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.DefaultStatusResponse
|
||||
|
@ -20,12 +19,11 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.
|
|||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.Duration
|
||||
import java.io.Serializable
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -107,6 +105,12 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
store()
|
||||
}
|
||||
|
||||
override val sameTimeZone: Boolean
|
||||
get() {
|
||||
val now = System.currentTimeMillis()
|
||||
return TimeZone.getDefault().getOffset(now) == timeZone.getOffset(now)
|
||||
}
|
||||
|
||||
override val bluetoothVersion: SoftwareVersion?
|
||||
get() = podState.bleVersion
|
||||
|
||||
|
@ -183,36 +187,40 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
override val lastStatusResponseReceived: Long
|
||||
get() = podState.lastStatusResponseReceived
|
||||
|
||||
override val time: DateTime?
|
||||
override val time: ZonedDateTime?
|
||||
get() {
|
||||
val minutesSinceActivation = podState.minutesSinceActivation
|
||||
val activationTime = podState.activationTime
|
||||
if ((activationTime != null) && (minutesSinceActivation != null)) {
|
||||
return DateTime(activationTime)
|
||||
.plusMinutes(minutesSinceActivation.toInt())
|
||||
.plus(Duration(podState.lastUpdatedSystem, System.currentTimeMillis()))
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(activationTime), timeZone.toZoneId())
|
||||
.plusMinutes(minutesSinceActivation.toLong())
|
||||
.plus(Duration.ofMillis(System.currentTimeMillis() - lastUpdatedSystem))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override val timeDrift: Duration?
|
||||
get() {
|
||||
return Duration(DateTime.now(), time)
|
||||
return Duration.between(ZonedDateTime.now(), time)
|
||||
}
|
||||
|
||||
override val expiry: DateTime?
|
||||
// TODO: Consider storing expiry datetime in pod state saving continuously recalculating to the same value
|
||||
override val expiry: ZonedDateTime?
|
||||
get() {
|
||||
val podLifeInHours = podLifeInHours
|
||||
val activationTime = podState.activationTime
|
||||
if (podLifeInHours != null && activationTime != null) {
|
||||
return DateTime(podState.activationTime).plusHours(podLifeInHours.toInt())
|
||||
val minutesSinceActivation = podState.minutesSinceActivation
|
||||
if (podLifeInHours != null && minutesSinceActivation != null) {
|
||||
return ZonedDateTime.now()
|
||||
.plusHours(podLifeInHours.toLong())
|
||||
.minusMinutes(minutesSinceActivation.toLong())
|
||||
.plus(Duration.ofMillis(System.currentTimeMillis() - lastUpdatedSystem))
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override var bluetoothConnectionState: OmnipodDashPodStateManager.BluetoothConnectionState
|
||||
@Synchronized
|
||||
get() = podState.bluetoothConnectionState
|
||||
@Synchronized
|
||||
set(bluetoothConnectionState) {
|
||||
podState.bluetoothConnectionState = bluetoothConnectionState
|
||||
rxBus.send(EventOmnipodDashPumpValuesChanged())
|
||||
|
@ -283,29 +291,29 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
requestedBolus: Double?
|
||||
):
|
||||
Single<OmnipodDashPodStateManager.ActiveCommand> {
|
||||
return Single.create { source ->
|
||||
if (activeCommand == null) {
|
||||
val command = OmnipodDashPodStateManager.ActiveCommand(
|
||||
podState.messageSequenceNumber,
|
||||
createdRealtime = SystemClock.elapsedRealtime(),
|
||||
historyId = historyId,
|
||||
sendError = null,
|
||||
basalProgram = basalProgram,
|
||||
tempBasal = tempBasal,
|
||||
requestedBolus = requestedBolus
|
||||
return Single.create { source ->
|
||||
if (activeCommand == null) {
|
||||
val command = OmnipodDashPodStateManager.ActiveCommand(
|
||||
podState.messageSequenceNumber,
|
||||
createdRealtime = SystemClock.elapsedRealtime(),
|
||||
historyId = historyId,
|
||||
sendError = null,
|
||||
basalProgram = basalProgram,
|
||||
tempBasal = tempBasal,
|
||||
requestedBolus = requestedBolus
|
||||
)
|
||||
podState.activeCommand = command
|
||||
source.onSuccess(command)
|
||||
} else {
|
||||
source.onError(
|
||||
java.lang.IllegalStateException(
|
||||
"Trying to send a command " +
|
||||
"and the last command was not confirmed"
|
||||
)
|
||||
podState.activeCommand = command
|
||||
source.onSuccess(command)
|
||||
} else {
|
||||
source.onError(
|
||||
java.lang.IllegalStateException(
|
||||
"Trying to send a command " +
|
||||
"and the last command was not confirmed"
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun observeNoActiveCommand(): Completable {
|
||||
|
@ -391,6 +399,32 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun differentAlertSettings(
|
||||
expirationReminderEnabled: Boolean,
|
||||
expirationHours: Int,
|
||||
lowReservoirAlertEnabled: Boolean,
|
||||
lowReservoirAlertUnits: Int
|
||||
): Boolean {
|
||||
return podState.expirationReminderEnabled == expirationReminderEnabled &&
|
||||
podState.expirationHours == expirationHours &&
|
||||
podState.lowReservoirAlertEnabled == lowReservoirAlertEnabled &&
|
||||
podState.lowReservoirAlertUnits == lowReservoirAlertUnits
|
||||
}
|
||||
|
||||
override fun updateExpirationAlertSettings(expirationReminderEnabled: Boolean, expirationHours: Int):
|
||||
Completable = Completable.defer {
|
||||
podState.expirationReminderEnabled = expirationReminderEnabled
|
||||
podState.expirationHours = expirationHours
|
||||
Completable.complete()
|
||||
}
|
||||
|
||||
override fun updateLowReservoirAlertSettings(lowReservoirAlertEnabled: Boolean, lowReservoirAlertUnits: Int):
|
||||
Completable = Completable.defer {
|
||||
podState.lowReservoirAlertEnabled = lowReservoirAlertEnabled
|
||||
podState.lowReservoirAlertUnits = lowReservoirAlertUnits
|
||||
Completable.complete()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getCommandConfirmationFromState(): CommandConfirmationFromState {
|
||||
return podState.activeCommand?.run {
|
||||
|
@ -429,7 +463,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
override fun onStart() {
|
||||
when (getCommandConfirmationFromState()) {
|
||||
CommandConfirmationSuccess, CommandConfirmationDenied -> {
|
||||
val now = System.currentTimeMillis()
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val newCommand = podState.activeCommand?.copy(
|
||||
createdRealtime = now,
|
||||
sentRealtime = now + 1
|
||||
|
@ -439,7 +473,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
}
|
||||
|
||||
CommandSendingNotConfirmed -> {
|
||||
val now = System.currentTimeMillis()
|
||||
val now = SystemClock.elapsedRealtime()
|
||||
val newCommand = podState.activeCommand?.copy(
|
||||
createdRealtime = now,
|
||||
sentRealtime = now + 1
|
||||
|
@ -611,6 +645,11 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
|
|||
var firstPrimeBolusVolume: Short? = null
|
||||
var secondPrimeBolusVolume: Short? = null
|
||||
|
||||
var expirationReminderEnabled: Boolean? = null
|
||||
var expirationHours: Int? = null
|
||||
var lowReservoirAlertEnabled: Boolean? = null
|
||||
var lowReservoirAlertUnits: Int? = null
|
||||
|
||||
var pulsesDelivered: Short? = null
|
||||
var pulsesRemaining: Short? = null
|
||||
var podStatus: PodStatus? = null
|
||||
|
|
|
@ -44,8 +44,8 @@ import info.nightscout.androidaps.utils.ui.UIRunnable
|
|||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.rxkotlin.plusAssign
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.Duration
|
||||
import java.time.Duration
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
@ -269,34 +269,41 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
|
||||
// Update time on Pod
|
||||
podInfoBinding.timeOnPod.text = podStateManager.time?.let {
|
||||
readableZonedTime(it)
|
||||
resourceHelper.gs(
|
||||
R.string.omnipod_common_time_with_timezone,
|
||||
dateUtil.dateAndTimeString(it.toEpochSecond() * 1000),
|
||||
podStateManager.timeZone.getDisplayName(true, TimeZone.SHORT)
|
||||
)
|
||||
} ?: PLACEHOLDER
|
||||
|
||||
val timeDeviationTooBig = podStateManager.timeDrift?.let {
|
||||
Duration.ofMinutes(MAX_TIME_DEVIATION_MINUTES).minus(
|
||||
it.abs()
|
||||
).isNegative
|
||||
} ?: false
|
||||
podInfoBinding.timeOnPod.setTextColor(
|
||||
podStateManager.timeDrift?.let {
|
||||
if (it.abs().isLongerThan(Duration.standardMinutes(MAX_TIME_DEVIATION_MINUTES))) {
|
||||
Color.RED
|
||||
} else {
|
||||
when {
|
||||
!podStateManager.sameTimeZone ->
|
||||
Color.MAGENTA
|
||||
timeDeviationTooBig ->
|
||||
Color.YELLOW
|
||||
else ->
|
||||
Color.WHITE
|
||||
}
|
||||
} ?: Color.WHITE
|
||||
}
|
||||
)
|
||||
|
||||
// Update Pod expiry time
|
||||
val expiresAt = podStateManager.expiry
|
||||
if (expiresAt == null) {
|
||||
podInfoBinding.podExpiryDate.text = PLACEHOLDER
|
||||
podInfoBinding.podExpiryDate.setTextColor(Color.WHITE)
|
||||
} else {
|
||||
podInfoBinding.podExpiryDate.text = readableZonedTime(expiresAt)
|
||||
podInfoBinding.podExpiryDate.setTextColor(
|
||||
if (DateTime.now().isAfter(expiresAt)) {
|
||||
Color.RED
|
||||
} else {
|
||||
Color.WHITE
|
||||
}
|
||||
)
|
||||
podInfoBinding.podExpiryDate.text = expiresAt?.let {
|
||||
dateUtil.dateAndTimeString(it.toEpochSecond() * 1000)
|
||||
}
|
||||
?: PLACEHOLDER
|
||||
podInfoBinding.podExpiryDate.setTextColor(
|
||||
if (expiresAt != null && ZonedDateTime.now().isAfter(expiresAt))
|
||||
Color.RED
|
||||
else
|
||||
Color.WHITE
|
||||
)
|
||||
|
||||
podStateManager.alarmType?.let {
|
||||
errors.add(
|
||||
|
@ -371,14 +378,14 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
private fun updateLastConnection() {
|
||||
if (podStateManager.isUniqueIdSet) {
|
||||
podInfoBinding.lastConnection.text = readableDuration(
|
||||
Duration(
|
||||
podStateManager.lastUpdatedSystem,
|
||||
System
|
||||
.currentTimeMillis()
|
||||
Duration.ofMillis(
|
||||
System.currentTimeMillis() -
|
||||
podStateManager.lastUpdatedSystem,
|
||||
|
||||
)
|
||||
)
|
||||
val lastConnectionColor =
|
||||
if (omnipodDashPumpPlugin.isUnreachableAlertTimeoutExceeded(getPumpUnreachableTimeout().millis)) {
|
||||
if (omnipodDashPumpPlugin.isUnreachableAlertTimeoutExceeded(getPumpUnreachableTimeout().toMillis())) {
|
||||
Color.RED
|
||||
} else {
|
||||
Color.WHITE
|
||||
|
@ -425,12 +432,14 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
val podStatusColor =
|
||||
if (!podStateManager.isActivationCompleted || podStateManager.isPodKaput || podStateManager.isSuspended) {
|
||||
val podStatusColor = when {
|
||||
!podStateManager.isActivationCompleted || podStateManager.isPodKaput || podStateManager.isSuspended ->
|
||||
Color.RED
|
||||
} else {
|
||||
podStateManager.activeCommand != null ->
|
||||
Color.YELLOW
|
||||
else ->
|
||||
Color.WHITE
|
||||
}
|
||||
}
|
||||
podInfoBinding.podStatus.setTextColor(podStatusColor)
|
||||
}
|
||||
|
||||
|
@ -444,7 +453,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
R.string.omnipod_common_overview_last_bolus_value,
|
||||
omnipodDashPumpPlugin.model().determineCorrectBolusSize(requestedBolus),
|
||||
resourceHelper.gs(R.string.insulin_unit_shortname),
|
||||
readableDuration(Duration(it.createdRealtime, SystemClock.elapsedRealtime()))
|
||||
readableDuration(Duration.ofMillis(SystemClock.elapsedRealtime() - it.createdRealtime))
|
||||
)
|
||||
text += " (uncertain) "
|
||||
textColor = Color.RED
|
||||
|
@ -464,7 +473,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
R.string.omnipod_common_overview_last_bolus_value,
|
||||
omnipodDashPumpPlugin.model().determineCorrectBolusSize(bolusSize),
|
||||
resourceHelper.gs(R.string.insulin_unit_shortname),
|
||||
readableDuration(Duration(it.startTime, System.currentTimeMillis()))
|
||||
readableDuration(Duration.ofMillis(System.currentTimeMillis() - it.startTime))
|
||||
)
|
||||
if (!it.deliveryComplete) {
|
||||
textColor = Color.YELLOW
|
||||
|
@ -600,47 +609,10 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// private fun getTimeZone(): DateTimeZone {
|
||||
// // return getSafe(() -> podState.getTimeZone());
|
||||
// return podStateManager.timeZone
|
||||
// }
|
||||
private fun getTimeZone(): TimeZone {
|
||||
// Return timezone ID (e.g "Europe/Amsterdam")
|
||||
return podStateManager.timeZone
|
||||
}
|
||||
|
||||
private fun readableZonedTime(time: DateTime): String {
|
||||
val timeAsJavaData = time.toLocalDateTime().toDate()
|
||||
return dateUtil.dateAndTimeString(timeAsJavaData.time)
|
||||
|
||||
// // TODO: Handle timeZone ID
|
||||
// val timeZone = getTimeZone()
|
||||
// if (timeZone == "") {
|
||||
// // No timezone defined, use local time (default)
|
||||
// return dateUtil.dateAndTimeString(timeAsJavaData.time)
|
||||
// }
|
||||
// else {
|
||||
// // Get full timezoned time
|
||||
// val isDaylightTime = timeZone.inDaylightTime(timeAsJavaData)
|
||||
// val locale = resources.configuration.locales.get(0)
|
||||
// val timeZoneDisplayName =
|
||||
// timeZone.getDisplayName(isDaylightTime, TimeZone.SHORT, locale) + " " + timeZone.getDisplayName(
|
||||
// isDaylightTime,
|
||||
// TimeZone.LONG,
|
||||
// locale
|
||||
// )
|
||||
// return resourceHelper.gs(
|
||||
// R.string.omnipod_common_time_with_timezone,
|
||||
// dateUtil.dateAndTimeString(timeAsJavaData.time),
|
||||
// timeZoneDisplayName
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
private fun readableDuration(duration: Duration): String {
|
||||
val hours = duration.standardHours.toInt()
|
||||
val minutes = duration.standardMinutes.toInt()
|
||||
val seconds = duration.standardSeconds.toInt()
|
||||
val hours = duration.toHours().toInt()
|
||||
val minutes = duration.toMinutes().toInt()
|
||||
val seconds = duration.seconds
|
||||
when {
|
||||
seconds < 10 -> {
|
||||
return resourceHelper.gs(R.string.omnipod_common_moments_ago)
|
||||
|
@ -683,7 +655,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
|
|||
|
||||
// FIXME ideally we should just have access to LocalAlertUtils here
|
||||
private fun getPumpUnreachableTimeout(): Duration {
|
||||
return Duration.standardMinutes(
|
||||
return Duration.ofMinutes(
|
||||
sp.getInt(
|
||||
R.string.key_pump_unreachable_threshold_minutes,
|
||||
Constants.DEFAULT_PUMP_UNREACHABLE_THRESHOLD_MINUTES
|
||||
|
|
|
@ -9,6 +9,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activati
|
|||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertTrigger
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import javax.inject.Inject
|
||||
|
@ -16,9 +18,10 @@ import javax.inject.Inject
|
|||
class DashInitializePodViewModel @Inject constructor(
|
||||
private val omnipodManager: OmnipodDashManager,
|
||||
injector: HasAndroidInjector,
|
||||
logger: AAPSLogger
|
||||
logger: AAPSLogger,
|
||||
private val sp: SP,
|
||||
private val podStateManager: OmnipodDashPodStateManager,
|
||||
) : InitializePodViewModel(injector, logger) {
|
||||
|
||||
override fun isPodInAlarm(): Boolean = false // TODO
|
||||
|
||||
override fun isPodActivationTimeExceeded(): Boolean = false // TODO
|
||||
|
@ -27,8 +30,14 @@ class DashInitializePodViewModel @Inject constructor(
|
|||
|
||||
override fun doExecuteAction(): Single<PumpEnactResult> =
|
||||
Single.create { source ->
|
||||
// TODO use configured value for low reservoir trigger
|
||||
val disposable = omnipodManager.activatePodPart1(AlertTrigger.ReservoirVolumeTrigger(200)).subscribeBy(
|
||||
val lowReservoirAlertEnabled = sp.getBoolean(R.string.key_omnipod_common_low_reservoir_alert_enabled, true)
|
||||
val lowReservoirAlertUnits = sp.getInt(R.string.key_omnipod_common_low_reservoir_alert_units, 10)
|
||||
val lowReservoirAlertTrigger = if (lowReservoirAlertEnabled) {
|
||||
AlertTrigger.ReservoirVolumeTrigger((lowReservoirAlertUnits * 10).toShort())
|
||||
} else
|
||||
null
|
||||
|
||||
val disposable = omnipodManager.activatePodPart1(lowReservoirAlertTrigger).subscribeBy(
|
||||
onNext = { podEvent ->
|
||||
logger.debug(
|
||||
LTag.PUMP,
|
||||
|
@ -41,6 +50,7 @@ class DashInitializePodViewModel @Inject constructor(
|
|||
},
|
||||
onComplete = {
|
||||
logger.debug("Pod activation part 1 completed")
|
||||
podStateManager.updateLowReservoirAlertSettings(lowReservoirAlertEnabled, lowReservoirAlertUnits)
|
||||
source.onSuccess(PumpEnactResult(injector).success(true))
|
||||
}
|
||||
)
|
||||
|
|
|
@ -8,12 +8,16 @@ import info.nightscout.androidaps.interfaces.ProfileFunction
|
|||
import info.nightscout.androidaps.interfaces.PumpSync
|
||||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
import info.nightscout.androidaps.logging.LTag
|
||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
|
||||
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
|
||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.activation.viewmodel.action.InsertCannulaViewModel
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.R
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.util.mapProfileToBasalProgram
|
||||
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import javax.inject.Inject
|
||||
|
@ -23,6 +27,9 @@ class DashInsertCannulaViewModel @Inject constructor(
|
|||
private val profileFunction: ProfileFunction,
|
||||
private val pumpSync: PumpSync,
|
||||
private val podStateManager: OmnipodDashPodStateManager,
|
||||
private val rxBus: RxBusWrapper,
|
||||
private val sp: SP,
|
||||
|
||||
injector: HasAndroidInjector,
|
||||
logger: AAPSLogger
|
||||
) : InsertCannulaViewModel(injector, logger) {
|
||||
|
@ -45,7 +52,15 @@ class DashInsertCannulaViewModel @Inject constructor(
|
|||
profile,
|
||||
basalProgram
|
||||
)
|
||||
val disposable = omnipodManager.activatePodPart2(basalProgram).subscribeBy(
|
||||
val expirationReminderEnabled = sp.getBoolean(R.string.key_omnipod_common_expiration_reminder_enabled, true)
|
||||
val expirationHours = sp.getInt(R.string.key_omnipod_common_expiration_reminder_hours_before_shutdown, 9)
|
||||
|
||||
val expirationHoursBeforeShutdown = if (expirationReminderEnabled)
|
||||
expirationHours.toLong()
|
||||
else
|
||||
null
|
||||
|
||||
val disposable = omnipodManager.activatePodPart2(basalProgram, expirationHoursBeforeShutdown).subscribeBy(
|
||||
onNext = { podEvent ->
|
||||
logger.debug(
|
||||
LTag.PUMP,
|
||||
|
@ -58,6 +73,7 @@ class DashInsertCannulaViewModel @Inject constructor(
|
|||
},
|
||||
onComplete = {
|
||||
logger.debug("Pod activation part 2 completed")
|
||||
podStateManager.basalProgram = basalProgram
|
||||
pumpSync.connectNewPump()
|
||||
pumpSync.insertTherapyEventIfNewWithTimestamp(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
|
@ -71,6 +87,14 @@ class DashInsertCannulaViewModel @Inject constructor(
|
|||
pumpType = PumpType.OMNIPOD_DASH,
|
||||
pumpSerial = podStateManager.uniqueId?.toString() ?: "n/a"
|
||||
)
|
||||
pumpSync.syncStopTemporaryBasalWithPumpId(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
endPumpId = System.currentTimeMillis(),
|
||||
pumpType = PumpType.OMNIPOD_DASH,
|
||||
pumpSerial = podStateManager.uniqueId?.toString() ?: "n/a"
|
||||
)
|
||||
podStateManager.updateExpirationAlertSettings(expirationReminderEnabled, expirationHours)
|
||||
rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED))
|
||||
source.onSuccess(PumpEnactResult(injector).success(true))
|
||||
}
|
||||
)
|
||||
|
|
|
@ -65,11 +65,33 @@
|
|||
validate:maxNumber="50"
|
||||
validate:minNumber="5"
|
||||
validate:testType="numericRange" />
|
||||
<!--
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/key_omnipod_common_automatically_silence_alerts_enabled"
|
||||
android:title="@string/omnipod_common_preferences_automatically_silence_alerts" />
|
||||
-->
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="@string/key_omnipod_common_preferences_category_notifications_settings"
|
||||
android:title="@string/omnipod_common_preferences_category_notifications"
|
||||
app:initialExpandedChildrenCount="0">
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/key_omnipod_common_automatically_silence_alerts_enabled"
|
||||
android:title="@string/omnipod_common_preferences_automatically_silence_alerts" />
|
||||
android:key="@string/key_omnipod_common_notification_uncertain_tbr_sound_enabled"
|
||||
android:title="@string/omnipod_common_preferences_notification_uncertain_tbr_sound_enabled" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/key_omnipod_common_notification_uncertain_smb_sound_enabled"
|
||||
android:title="@string/omnipod_common_preferences_notification_uncertain_smb_sound_enabled" />
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="@string/key_omnipod_common_notification_uncertain_bolus_sound_enabled"
|
||||
android:title="@string/omnipod_common_preferences_notification_uncertain_bolus_sound_enabled" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
|
Loading…
Reference in a new issue