- extended pump-common package a little, so that in the future we can abstract some of stuff that is used by all (most) of drivers: History Dialog, BLE_Discovery, events and some other stuff (I used this with my new Ypso driver and I want to use the same with Tslim)

This commit is contained in:
Andy Rozman 2022-07-03 15:34:33 +01:00
parent ad89d54319
commit 53c041f5cf
34 changed files with 1549 additions and 38 deletions

View file

@ -194,7 +194,11 @@ class MedtronicPumpPlugin @Inject constructor(
} }
} }
override fun onStartCustomActions() { override fun hasService(): Boolean {
return true
}
override fun onStartScheduledPumpActions() {
// check status every minute (if any status needs refresh we send readStatus command) // check status every minute (if any status needs refresh we send readStatus command)
Thread { Thread {
@ -673,7 +677,7 @@ class MedtronicPumpPlugin @Inject constructor(
} }
// if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged), // if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged),
// if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed
@Synchronized @Synchronized
override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType): PumpEnactResult { override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: TemporaryBasalType): PumpEnactResult {
setRefreshButtonEnabled(false) setRefreshButtonEnabled(false)
@ -743,7 +747,7 @@ class MedtronicPumpPlugin @Inject constructor(
PumpEnactResult(injector).success(false).enacted(false) // PumpEnactResult(injector).success(false).enacted(false) //
.comment(R.string.medtronic_cmd_tbr_could_not_be_delivered) .comment(R.string.medtronic_cmd_tbr_could_not_be_delivered)
} else { } else {
medtronicPumpStatus.tempBasalStart = Date() medtronicPumpStatus.tempBasalStart = System.currentTimeMillis()
medtronicPumpStatus.tempBasalAmount = absoluteRate medtronicPumpStatus.tempBasalAmount = absoluteRate
medtronicPumpStatus.tempBasalLength = durationInMinutes medtronicPumpStatus.tempBasalLength = durationInMinutes

View file

@ -122,7 +122,7 @@ class MedtronicPumpStatus @Inject constructor(private val rh: ResourceHelper,
get() { get() {
if (tempBasalStart == null) return null if (tempBasalStart == null) return null
if (tempBasalEnd == null) { if (tempBasalEnd == null) {
val startTime = tempBasalStart!!.time val startTime = tempBasalStart!!
tempBasalEnd = startTime + tempBasalLength!! * 60 * 1000 tempBasalEnd = startTime + tempBasalLength!! * 60 * 1000
} }
if (System.currentTimeMillis() > tempBasalEnd!!) { if (System.currentTimeMillis() > tempBasalEnd!!) {

View file

@ -1,6 +1,18 @@
<manifest> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="info.nightscout.androidaps.plugins.pump.common">
<application> <application>
<activity
android:name="info.nightscout.androidaps.plugins.pump.common.ui.PumpBLEConfigActivity"
android:theme="@style/AppTheme"
android:exported="false">
<intent-filter>
<action android:name="info.nightscout.androidaps.plugins.pump.common.ui.PumpBLEConfigActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ui.PumpHistoryActivity" />
</application> </application>

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.text.format.DateFormat import android.text.format.DateFormat
import com.google.gson.GsonBuilder
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.data.PumpEnactResult
@ -25,7 +26,6 @@ import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DecimalFormatter.to0Decimal import info.nightscout.androidaps.utils.DecimalFormatter.to0Decimal
import info.nightscout.androidaps.utils.DecimalFormatter.to2Decimal import info.nightscout.androidaps.utils.DecimalFormatter.to2Decimal
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag import info.nightscout.shared.logging.LTag
@ -56,7 +56,7 @@ abstract class PumpPluginAbstract protected constructor(
var pumpSyncStorage: info.nightscout.androidaps.plugins.pump.common.sync.PumpSyncStorage var pumpSyncStorage: info.nightscout.androidaps.plugins.pump.common.sync.PumpSyncStorage
) : PumpPluginBase(pluginDescription!!, injector!!, aapsLogger, rh, commandQueue), Pump, Constraints, info.nightscout.androidaps.plugins.pump.common.sync.PumpSyncEntriesCreator { ) : PumpPluginBase(pluginDescription!!, injector!!, aapsLogger, rh, commandQueue), Pump, Constraints, info.nightscout.androidaps.plugins.pump.common.sync.PumpSyncEntriesCreator {
private val disposable = CompositeDisposable() val disposable = CompositeDisposable()
// Pump capabilities // Pump capabilities
final override var pumpDescription = PumpDescription() final override var pumpDescription = PumpDescription()
@ -68,30 +68,41 @@ abstract class PumpPluginAbstract protected constructor(
protected var displayConnectionMessages = false protected var displayConnectionMessages = false
var pumpType: PumpType = PumpType.GENERIC_AAPS var pumpType: PumpType = PumpType.GENERIC_AAPS
get() = field
set(value) { set(value) {
field = value field = value
pumpDescription.fillFor(value) pumpDescription.fillFor(value)
} }
protected var gson = GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
abstract fun initPumpStatusData() abstract fun initPumpStatusData()
open fun hasService(): Boolean {
return true
}
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
initPumpStatusData() initPumpStatusData()
val intent = Intent(context, serviceClass) if (hasService()) {
context.bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE) val intent = Intent(context, serviceClass)
context.bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE)
disposable.add(rxBus
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ _ -> context.unbindService(serviceConnection!!) }) { throwable: Throwable? -> fabricPrivacy.logException(throwable!!) }
)
}
serviceRunning = true serviceRunning = true
disposable.add(rxBus onStartScheduledPumpActions()
.toObservable(EventAppExit::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ _ -> context.unbindService(serviceConnection!!) }) { throwable: Throwable? -> fabricPrivacy.logException(throwable!!) }
)
onStartCustomActions()
} }
override fun onStop() { override fun onStop() {
aapsLogger.debug(LTag.PUMP, deviceID() + " onStop()") aapsLogger.debug(LTag.PUMP, model().model + " onStop()")
context.unbindService(serviceConnection!!) if (hasService()) {
context.unbindService(serviceConnection!!)
}
serviceRunning = false serviceRunning = false
disposable.clear() disposable.clear()
super.onStop() super.onStop()
@ -100,7 +111,7 @@ abstract class PumpPluginAbstract protected constructor(
/** /**
* If we need to run any custom actions in onStart (triggering events, etc) * If we need to run any custom actions in onStart (triggering events, etc)
*/ */
abstract fun onStartCustomActions() abstract fun onStartScheduledPumpActions()
/** /**
* Service class (same one you did serviceConnection for) * Service class (same one you did serviceConnection for)
@ -231,7 +242,8 @@ abstract class PumpPluginAbstract protected constructor(
val extended = JSONObject() val extended = JSONObject()
try { try {
battery.put("percent", pumpStatusData.batteryRemaining) battery.put("percent", pumpStatusData.batteryRemaining)
status.put("status", pumpStatusData.pumpStatusType.status) // TODO check this status.put("status", pumpStatusData.pumpStatusType.status)
status.put("status", pumpStatusData.pumpRunningState.status)
extended.put("Version", version) extended.put("Version", version)
try { try {
extended.put("ActiveProfile", profileName) extended.put("ActiveProfile", profileName)
@ -324,6 +336,7 @@ abstract class PumpPluginAbstract protected constructor(
// bolus needed, ask pump to deliver it // bolus needed, ask pump to deliver it
deliverBolus(detailedBolusInfo) deliverBolus(detailedBolusInfo)
} else { } else {
detailedBolusInfo.timestamp = System.currentTimeMillis()
// no bolus required, carb only treatment // no bolus required, carb only treatment
pumpSyncStorage.addCarbs(PumpDbEntryCarbs(detailedBolusInfo, this)) pumpSyncStorage.addCarbs(PumpDbEntryCarbs(detailedBolusInfo, this))
@ -360,4 +373,4 @@ abstract class PumpPluginAbstract protected constructor(
pumpDescription.fillFor(pumpType) pumpDescription.fillFor(pumpType)
this.pumpType = pumpType this.pumpType = pumpType
} }
} }

View file

@ -0,0 +1,77 @@
package info.nightscout.androidaps.plugins.pump.common.ble
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.content.Intent
import androidx.annotation.StringRes
import com.google.gson.Gson
import dagger.android.DaggerBroadcastReceiver
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.pump.common.events.EventPumpConnectionParametersChanged
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import javax.inject.Inject
class BondStateReceiver(
@StringRes var deviceAddress: Int,
@StringRes var bondedFlag: Int,
var targetDevice: String,
var targetState: Int
) : DaggerBroadcastReceiver() {
@Inject lateinit var sp: SP
@Inject lateinit var context: Context
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBus
var TAG = LTag.PUMPBTCOMM
var gson = Gson()
var applicationContext: Context? = null
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
val action = intent.action
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
aapsLogger.info(TAG, "in onReceive: INTENT" + gson.toJson(intent))
if (device == null) {
aapsLogger.error(TAG, "onReceive. Device is null. Exiting.")
return
} else {
if (device.address != targetDevice) {
aapsLogger.error(TAG, "onReceive. Device is not the same as targetDevice. Exiting.")
return
}
}
// Check if action is valid
if (action == null) return
// Take action depending on new bond state
if (action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
val previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1)
aapsLogger.info(TAG, "in onReceive: bondState=$bondState, previousBondState=$previousBondState")
if (bondState == targetState) {
aapsLogger.info(TAG, "onReceive: found targeted state: $targetState")
val currentDeviceSettings = sp.getString(deviceAddress, "")
if (currentDeviceSettings.equals(targetDevice)) {
if (targetState == 12) {
sp.putBoolean(bondedFlag, true)
rxBus.send(EventPumpConnectionParametersChanged())
} else if (targetState == 10) {
sp.putBoolean(bondedFlag, false)
rxBus.send(EventPumpConnectionParametersChanged())
}
context.unregisterReceiver(this)
} else {
aapsLogger.error(TAG, "onReceive: Device stored in SP is not the same as target device, process interrupted")
}
} else {
aapsLogger.info(TAG, "onReceive: currentBondState=$bondState, targetBondState=$targetState")
}
}
}
}

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.pump.common.data package info.nightscout.androidaps.plugins.pump.common.data
import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType import info.nightscout.androidaps.plugins.pump.common.defs.PumpRunningState
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import java.util.* import java.util.*
@ -33,12 +33,13 @@ abstract class PumpStatus(var pumpType: PumpType) {
var dailyTotalUnits: Double? = null var dailyTotalUnits: Double? = null
var maxDailyTotalUnits: String? = null var maxDailyTotalUnits: String? = null
var units: String? = null // Constants.MGDL or Constants.MMOL var units: String? = null // Constants.MGDL or Constants.MMOL
var pumpStatusType = PumpStatusType.Running var pumpRunningState = PumpRunningState.Running
var basalsByHour: DoubleArray? = null var basalsByHour: DoubleArray? = null
var tempBasalStart: Date? = null var tempBasalStart: Long? = null
var tempBasalAmount: Double? = 0.0 var tempBasalAmount: Double? = 0.0
var tempBasalLength: Int? = 0 var tempBasalLength: Int? = 0
var tempBasalEnd: Long? = null var tempBasalEnd: Long? = null
var pumpTime: PumpTimeDifferenceDto? = null
abstract fun initSettings() abstract fun initSettings()

View file

@ -0,0 +1,25 @@
package info.nightscout.androidaps.plugins.pump.common.data
import org.joda.time.DateTime
import org.joda.time.Seconds
/**
* Created by andy on 28/05/2021.
*/
class PumpTimeDifferenceDto constructor(var localDeviceTime: DateTime,
var pumpTime: DateTime) {
var timeDifference = 0
fun calculateDifference() {
val secondsBetween = Seconds.secondsBetween(localDeviceTime, pumpTime)
timeDifference = secondsBetween.seconds
// val diff = localDeviceTime - pumpTime
// timeDifference = (diff / 1000.0).toInt()
}
init {
calculateDifference()
}
}

View file

@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.pump.common.defs;
/**
* Created by andy on 1/20/19.
*/
public enum BasalProfileStatus {
NotInitialized, //
ProfileOK, //
ProfileChanged, //
;
}

View file

@ -1,14 +1,26 @@
package info.nightscout.androidaps.plugins.pump.common.defs package info.nightscout.androidaps.plugins.pump.common.defs
enum class PumpDriverState { import info.nightscout.androidaps.plugins.pump.common.R
NotInitialized, // TODO there are 3 classes now, that do similar things, sort of, need to define exact rules: PumpDeviceState, PumpDriverState, PumpStatusState
Connecting,
Connected, // TODO split this enum into 2
Initialized, enum class PumpDriverState(var resourceId: Int) {
Ready, Busy,
Suspended; NotInitialized(R.string.pump_status_not_initialized), // this state should be set only when driver is created
Connecting(R.string.connecting), //
Connected(R.string.connected), //
Initialized(R.string.pump_status_initialized), // this is weird state that probably won't be used, since its more driver centric that communication centric
EncryptCommunication(R.string.pump_status_encrypt), //
Ready(R.string.pump_status_ready),
Busy(R.string.pump_status_busy), //
Suspended(R.string.pump_status_suspended), //
Sleeping(R.string.pump_status_sleeping),
ExecutingCommand(R.string.pump_status_executing_command),
Disconnecting(R.string.disconnecting),
Disconnected(R.string.disconnected),
ErrorCommunicatingWithPump(R.string.pump_status_error_comm);
fun isConnected(): Boolean = this == Connected || this == Initialized || this == Busy || this == Suspended fun isConnected(): Boolean = this == Connected || this == Initialized || this == Busy || this == Suspended
fun isInitialized(): Boolean = this == Initialized || this == Busy || this == Suspended fun isInitialized(): Boolean = this == Initialized || this == Busy || this == Suspended
} }

View file

@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.pump.common.defs
import info.nightscout.androidaps.plugins.pump.common.R import info.nightscout.androidaps.plugins.pump.common.R
import info.nightscout.androidaps.interfaces.ResourceHelper import info.nightscout.androidaps.interfaces.ResourceHelper
import java.util.* import kotlin.streams.toList
/** /**
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
@ -11,9 +11,10 @@ import java.util.*
* *
* Author: Andy {andy.rozman@gmail.com} * Author: Andy {andy.rozman@gmail.com}
*/ */
enum class PumpHistoryEntryGroup(val resourceId: Int) { enum class PumpHistoryEntryGroup(val resourceId: Int, val pumpTypeGroupConfig: PumpTypeGroupConfig = PumpTypeGroupConfig.All) {
All(R.string.history_group_all), All(R.string.history_group_all),
Base(R.string.history_group_base),
Bolus(R.string.history_group_bolus), Bolus(R.string.history_group_bolus),
Basal(R.string.history_group_basal), Basal(R.string.history_group_basal),
Prime(R.string.history_group_prime), Prime(R.string.history_group_prime),
@ -22,7 +23,14 @@ enum class PumpHistoryEntryGroup(val resourceId: Int) {
Glucose(R.string.history_group_glucose), Glucose(R.string.history_group_glucose),
Notification(R.string.history_group_notification), Notification(R.string.history_group_notification),
Statistic(R.string.history_group_statistic), Statistic(R.string.history_group_statistic),
Unknown(R.string.history_group_unknown); Other(R.string.history_group_other),
Unknown(R.string.history_group_unknown),
// Ypso
EventsOnly(R.string.history_group_events),
EventsNoStat(R.string.history_group_events_no_stat)
;
var translated: String? = null var translated: String? = null
private set private set
@ -33,9 +41,10 @@ enum class PumpHistoryEntryGroup(val resourceId: Int) {
companion object { companion object {
private var translatedList: MutableList<PumpHistoryEntryGroup>? = null @JvmStatic private var translatedList: MutableList<PumpHistoryEntryGroup>? = null
private fun doTranslation(rh: ResourceHelper) { fun doTranslation(rh: ResourceHelper) {
if (translatedList != null) return
translatedList = ArrayList() translatedList = ArrayList()
for (pumpHistoryEntryGroup in values()) { for (pumpHistoryEntryGroup in values()) {
pumpHistoryEntryGroup.translated = rh.gs(pumpHistoryEntryGroup.resourceId) pumpHistoryEntryGroup.translated = rh.gs(pumpHistoryEntryGroup.resourceId)
@ -43,9 +52,27 @@ enum class PumpHistoryEntryGroup(val resourceId: Int) {
} }
} }
// FIXME this is just for Java compatibility reasons (can be removed when all drivers using it are in Kotlin - OmnipodEros still in java)
fun getTranslatedList(rh: ResourceHelper): List<PumpHistoryEntryGroup> { fun getTranslatedList(rh: ResourceHelper): List<PumpHistoryEntryGroup> {
return getTranslatedList(rh, PumpTypeGroupConfig.All)
}
fun getTranslatedList(rh: ResourceHelper, pumpTypeGroupConfig: PumpTypeGroupConfig = PumpTypeGroupConfig.All): List<PumpHistoryEntryGroup> {
if (translatedList == null) doTranslation(rh) if (translatedList == null) doTranslation(rh)
return translatedList!!
val outList: List<PumpHistoryEntryGroup>
if (pumpTypeGroupConfig == PumpTypeGroupConfig.All) {
outList = translatedList!!.stream()
.filter { pre -> pre.pumpTypeGroupConfig == PumpTypeGroupConfig.All }
.toList();
} else {
outList = translatedList!!.stream()
.filter { pre -> (pre.pumpTypeGroupConfig == PumpTypeGroupConfig.All || pre.pumpTypeGroupConfig == pumpTypeGroupConfig) }
.toList();
}
return outList
} }
} }

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.common.defs
// TODO there are 3 classes now, that do similar things, sort of, need to define exact rules: PumpDeviceState, PumpDriverState, PumpStatusState
enum class PumpRunningState(val status: String) {
Running("normal"),
Suspended("suspended");
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.pump.common.defs
enum class PumpTypeGroupConfig {
All,
Medtronic,
OmnipodEros,
YpsoPump
}

View file

@ -0,0 +1,49 @@
package info.nightscout.androidaps.plugins.pump.common.defs
import java.util.*
enum class PumpUpdateFragmentType {
None,
PumpStatus,
DriverStatus,
Queue,
Bolus,
TBR,
ProfileChange,
TBRCount,
BolusCount,
TreatmentValues(Arrays.asList(Bolus, TBR, TBRCount, BolusCount, ProfileChange)), // Last Bolus, TBR, Profile Change, TBR Count, Bolus Count
Full,
Configuration, // Firmware, Errors
Battery,
Reservoir,
OtherValues(Arrays.asList(Battery, Reservoir)), // Battery, Reservoir
Custom_1,
Custom_2,
Custom_3,
Custom_4,
Custom_5,
Custom_6,
Custom_7,
Custom_8
;
final var children: List<PumpUpdateFragmentType>? = null
constructor() {
}
constructor(children: List<PumpUpdateFragmentType>) {
this.children = children;
}
fun isOptionIncluded(type: PumpUpdateFragmentType): Boolean {
if (this == type)
return true
else if (this.children != null && this.children!!.contains(type))
return true;
return false;
}
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.common.driver
import info.nightscout.androidaps.plugins.pump.common.driver.history.PumpHistoryDataProvider
interface PumpDriverConfiguration {
fun getPumpBLESelector(): PumpBLESelector
fun getPumpHistoryDataProvider(): PumpHistoryDataProvider
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.common.driver
interface PumpDriverConfigurationCapable {
fun getPumpDriverConfiguration(): PumpDriverConfiguration
}

View file

@ -0,0 +1,105 @@
package info.nightscout.androidaps.plugins.pump.common.driver
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Context
interface PumpBLESelector {
/**
* Called on resume
*/
fun onResume()
/**
* Called on destory
*/
fun onDestroy()
/**
* This method is called when device is being removed (it can be empty if you don't need to do any special action, but if you
* have to unbound (for example), then this is method where to call it. For unbounding removeBond is available
*/
fun removeDevice(device: BluetoothDevice)
/**
* Cleanup method after device was removed
*/
fun cleanupAfterDeviceRemoved()
/**
* operations when scan failed
*/
fun onScanFailed(context: Context, errorCode: Int)
/**
* operations when scan starts
*/
fun onStartLeDeviceScan(context: Context)
/**
* operations when scan stops
*/
fun onStopLeDeviceScan(context: Context)
/**
* operations when scan was stopped manualy (press on button)
*/
fun onManualStopLeDeviceScan(context: Context)
/**
* operations when on non manual stop of scan (on timeout)
*/
fun onNonManualStopLeDeviceScan(context: Context)
/**
* get Scan Filters
*/
fun getScanFilters(): List<ScanFilter>?
/**
* get Scan Settings
*/
fun getScanSettings(): ScanSettings?
/**
* filter device on search (for cases where we can't do it with Scan Filters
*/
fun filterDevice(device: BluetoothDevice): BluetoothDevice?
/**
* operations when device selected
*/
fun onDeviceSelected(bluetoothDevice: BluetoothDevice, bleAddress: String, deviceName: String)
/**
* If pump has no name, this name will be used
*/
fun getUnknownPumpName(): String
/**
* get Address of Currently selected pump, empty string if none
*/
fun currentlySelectedPumpAddress(): String
/**
* get Name of Currently selected pump, getUnknownPumpName() string if none
*/
fun currentlySelectedPumpName(): String
/**
* Get Translation Text
*/
fun getText(key: PumpBLESelectorText): String
}
enum class PumpBLESelectorText {
SCAN_TITLE,
SELECTED_PUMP_TITLE,
REMOVE_TITLE,
REMOVE_TEXT,
NO_SELECTED_PUMP,
PUMP_CONFIGURATION
}

View file

@ -0,0 +1,108 @@
package info.nightscout.androidaps.plugins.pump.common.driver.ble
import android.bluetooth.BluetoothDevice
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.widget.Toast
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.pump.common.R
import info.nightscout.androidaps.plugins.pump.common.driver.PumpBLESelector
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
abstract class PumpBLESelectorAbstract constructor(
var resourceHelper: ResourceHelper,
var aapsLogger: AAPSLogger,
var sp: SP,
var rxBus: RxBus,
var context: Context
) : PumpBLESelector {
protected val TAG = LTag.PUMPBTCOMM
override fun getScanSettings(): ScanSettings? {
return ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
}
override fun getScanFilters(): MutableList<ScanFilter>? {
return null
}
override fun filterDevice(device: BluetoothDevice): BluetoothDevice? {
return device
}
override fun onResume() {
}
override fun onDestroy() {
}
override fun removeDevice(device: BluetoothDevice) {
}
override fun cleanupAfterDeviceRemoved() {
}
override fun onManualStopLeDeviceScan(context: Context) {
}
override fun onNonManualStopLeDeviceScan(context: Context) {
}
//fun onDeviceSelected(bluetoothDevice: BluetoothDevice, bleAddress: String, deviceName: String)
override fun onScanFailed(context: Context, errorCode: Int) {
Toast.makeText(
context, resourceHelper.gs(R.string.ble_config_scan_error, errorCode),
Toast.LENGTH_LONG
).show()
}
override fun onStartLeDeviceScan(context: Context) {
Toast.makeText(context, R.string.ble_config_scan_scanning, Toast.LENGTH_SHORT).show()
}
override fun onStopLeDeviceScan(context: Context) {
Toast.makeText(context, R.string.ble_config_scan_finished, Toast.LENGTH_SHORT).show()
}
protected fun removeBond(bluetoothDevice: BluetoothDevice): Boolean {
return try {
val method = bluetoothDevice.javaClass.getMethod("removeBond")
val resultObject = method.invoke(bluetoothDevice)
if (resultObject == null) {
aapsLogger.error(TAG, "ERROR: result object is null")
false
} else {
val result = resultObject as Boolean
if (result) {
aapsLogger.info(TAG, "Successfully removed bond")
} else {
aapsLogger.warn(TAG, "Bond was not removed")
}
result
}
} catch (e: Exception) {
aapsLogger.error(TAG, "ERROR: could not remove bond")
e.printStackTrace()
false
}
}
protected fun getBondingStatusDescription(state: Int): String {
return if (state == 10) {
"BOND_NONE"
} else if (state == 11) {
"BOND_BONDING"
} else if (state == 12) {
"BOND_BONDED"
} else {
"UNKNOWN BOND STATUS ($state)"
}
}
}

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.plugins.pump.common.driver.history
interface PumpDataConverter {
}

View file

@ -0,0 +1,75 @@
package info.nightscout.androidaps.plugins.pump.common.driver.history
import androidx.annotation.StringRes
import info.nightscout.androidaps.plugins.pump.common.R
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup
interface PumpHistoryDataProvider {
/**
* Get Data, specified with PumpHistoryPeriod
*/
fun getData(period: PumpHistoryPeriod): List<PumpHistoryEntry>
/**
* Get Initial Period
*/
fun getInitialPeriod(): PumpHistoryPeriod
/**
* Get InitialData
*/
fun getInitialData(): List<PumpHistoryEntry>
/**
* Get Allowed Pump History Groups (for specific pump)
*/
fun getAllowedPumpHistoryGroups(): List<PumpHistoryEntryGroup>
/**
* Get Spinner Width in pixels (same as specifying 150dp)
*/
fun getSpinnerWidthInPixels(): Int
/**
* Get Translation Text
*/
fun getText(key: PumpHistoryText): String
/**
* For filtering of items
*/
fun isItemInSelection(itemGroup: PumpHistoryEntryGroup, targetGroup: PumpHistoryEntryGroup): Boolean
}
enum class PumpHistoryPeriod constructor(
@StringRes var stringId: Int,
var isHours: Boolean = false
) {
TODAY(R.string.time_today),
LAST_HOUR(R.string.time_last_hour, true),
LAST_3_HOURS(R.string.time_last_3_hours, true),
LAST_6_HOURS(R.string.time_last_6_hours, true),
LAST_12_HOURS(R.string.time_last_12_hours, true),
LAST_2_DAYS(R.string.time_last_2_days),
LAST_4_DAYS(R.string.time_last_4_days),
LAST_WEEK(R.string.time_last_week),
LAST_MONTH(R.string.time_last_month),
ALL(R.string.history_group_all)
}
enum class PumpHistoryText {
PUMP_HISTORY,
// OLD ONES
SCAN_TITLE,
SELECTED_PUMP_TITLE,
REMOVE_TITLE,
REMOVE_TEXT,
NO_SELECTED_PUMP,
PUMP_CONFIGURATION
}

View file

@ -0,0 +1,46 @@
package info.nightscout.androidaps.plugins.pump.common.driver.history
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup
import java.util.*
abstract class PumpHistoryDataProviderAbstract : PumpHistoryDataProvider {
override fun getInitialData(): List<PumpHistoryEntry> {
return getData(getInitialPeriod());
}
override fun getSpinnerWidthInPixels(): Int {
return 150
}
protected fun getStartingTimeForData(period: PumpHistoryPeriod): Long {
val gregorianCalendar = GregorianCalendar()
if (!period.isHours) {
gregorianCalendar.set(Calendar.HOUR_OF_DAY, 0)
gregorianCalendar.set(Calendar.MINUTE, 0)
gregorianCalendar.set(Calendar.SECOND, 0)
gregorianCalendar.set(Calendar.MILLISECOND, 0)
}
when (period) {
PumpHistoryPeriod.TODAY -> return gregorianCalendar.timeInMillis
PumpHistoryPeriod.ALL -> return 0L
PumpHistoryPeriod.LAST_2_DAYS -> gregorianCalendar.add(Calendar.DAY_OF_MONTH, -1)
PumpHistoryPeriod.LAST_4_DAYS -> gregorianCalendar.add(Calendar.DAY_OF_MONTH, -3)
PumpHistoryPeriod.LAST_WEEK -> gregorianCalendar.add(Calendar.WEEK_OF_YEAR, -1)
PumpHistoryPeriod.LAST_MONTH -> gregorianCalendar.add(Calendar.MONTH, -1)
PumpHistoryPeriod.LAST_HOUR -> gregorianCalendar.add(Calendar.HOUR_OF_DAY, -1)
PumpHistoryPeriod.LAST_3_HOURS -> gregorianCalendar.add(Calendar.HOUR_OF_DAY, -3)
PumpHistoryPeriod.LAST_6_HOURS -> gregorianCalendar.add(Calendar.HOUR_OF_DAY, -6)
PumpHistoryPeriod.LAST_12_HOURS -> gregorianCalendar.add(Calendar.HOUR_OF_DAY, -12)
}
return gregorianCalendar.timeInMillis
}
override fun isItemInSelection(itemGroup: PumpHistoryEntryGroup, targetGroup: PumpHistoryEntryGroup): Boolean {
return itemGroup === targetGroup
}
}

View file

@ -0,0 +1,18 @@
package info.nightscout.androidaps.plugins.pump.common.driver.history
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup
import info.nightscout.androidaps.interfaces.ResourceHelper
interface PumpHistoryEntry {
fun prepareEntryData(resourceHelper: ResourceHelper, pumpDataConverter: PumpDataConverter)
fun getEntryDateTime(): String
fun getEntryType(): String
fun getEntryValue(): String
fun getEntryTypeGroup(): PumpHistoryEntryGroup
}

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.pump.common.events
import info.nightscout.androidaps.events.Event
class EventBondChanged(
var connectionAddress: String,
var bondStatus: Boolean
) : Event()

View file

@ -0,0 +1,8 @@
package info.nightscout.androidaps.plugins.pump.common.events
import info.nightscout.androidaps.events.Event
class EventPumpChanged(var serialNumber: String,
var connectionAddress: String,
var parameters: MutableMap<String, Any>? = null) : Event() {
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.pump.common.events
import info.nightscout.androidaps.events.Event
class EventPumpConnectionParametersChanged : Event() {
}

View file

@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.pump.common.events
import info.nightscout.androidaps.events.Event
import info.nightscout.androidaps.plugins.pump.common.defs.PumpUpdateFragmentType
class EventPumpFragmentValuesChanged : Event {
var updateType: PumpUpdateFragmentType = PumpUpdateFragmentType.None
constructor(updateType: PumpUpdateFragmentType) {
this.updateType = updateType
}
}

View file

@ -0,0 +1,338 @@
package info.nightscout.androidaps.plugins.pump.common.ui
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.BaseAdapter
import android.widget.TextView
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.pump.common.R
import info.nightscout.androidaps.plugins.pump.common.ble.BlePreCheck
import info.nightscout.androidaps.plugins.pump.common.databinding.PumpBleConfigActivityBinding
import info.nightscout.androidaps.plugins.pump.common.driver.PumpBLESelector
import info.nightscout.androidaps.plugins.pump.common.driver.PumpBLESelectorText
import info.nightscout.androidaps.plugins.pump.common.driver.PumpDriverConfigurationCapable
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import org.apache.commons.lang3.StringUtils
import javax.inject.Inject
@SuppressLint("MissingPermission")
class PumpBLEConfigActivity : DaggerAppCompatActivity() {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var sp: SP
@Inject lateinit var blePreCheck: BlePreCheck
@Inject lateinit var context: Context
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBus
private lateinit var binding: PumpBleConfigActivityBinding
private lateinit var bleSelector: PumpBLESelector
private var settings: ScanSettings? = null
private var filters: List<ScanFilter>? = null
private var bleScanner: BluetoothLeScanner? = null
private var deviceListAdapter = LeDeviceListAdapter()
private val handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private val bluetoothAdapter: BluetoothAdapter? get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager?)?.adapter
var scanning = false
private val devicesMap: MutableMap<String, BluetoothDevice> = HashMap()
private val stopScanAfterTimeoutRunnable = Runnable {
if (scanning) {
stopLeDeviceScan(false)
}
}
@SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PumpBleConfigActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
if (!blePreCheck.prerequisitesCheck(this)) {
aapsLogger.error(TAG, "prerequisitesCheck failed.")
finish()
return
}
// Configuration
val activePump = activePlugin.activePump
if (activePump is PumpDriverConfigurationCapable) {
bleSelector = activePump.getPumpDriverConfiguration().getPumpBLESelector()
} else {
throw RuntimeException("PumpBLEConfigActivity can be used only with PumpDriverConfigurationCapable pump driver.")
}
binding.pumpBleConfigCurrentlySelectedText.text = bleSelector.getText(PumpBLESelectorText.SELECTED_PUMP_TITLE)
binding.pumpBleConfigScanTitle.text = bleSelector.getText(PumpBLESelectorText.SCAN_TITLE)
title = bleSelector.getText(PumpBLESelectorText.PUMP_CONFIGURATION)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
binding.pumpBleConfigScanDeviceList.adapter = deviceListAdapter
binding.pumpBleConfigScanDeviceList.onItemClickListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, _: Long ->
// stop scanning if still active
if (scanning) {
stopLeDeviceScan(true)
}
val bleAddress = (view.findViewById<View>(R.id.pump_ble_config_scan_item_device_address) as TextView).text.toString()
val deviceName = (view.findViewById<View>(R.id.pump_ble_config_scan_item_device_name) as TextView).text.toString()
if (devicesMap.containsKey(bleAddress)) {
aapsLogger.debug(TAG, "Device FOUND in deviceMap: $bleAddress")
val bluetoothDevice = devicesMap[bleAddress]
bleSelector.onDeviceSelected(bluetoothDevice!!, bleAddress, deviceName)
} else {
aapsLogger.debug(TAG, "Device NOT found in deviceMap: $bleAddress")
}
finish()
}
binding.pumpBleConfigScanStart.setOnClickListener { startLeDeviceScan() }
binding.pumpBleConfigButtonScanStop.setOnClickListener {
if (scanning) {
stopLeDeviceScan(true)
}
}
binding.pumpBleConfigButtonRemove.setOnClickListener {
OKDialog.showConfirmation(
this@PumpBLEConfigActivity,
bleSelector.getText(PumpBLESelectorText.REMOVE_TITLE),
bleSelector.getText(PumpBLESelectorText.REMOVE_TEXT),
Runnable {
val deviceAddress: String = binding.pumpBleConfigCurrentlySelectedPumpAddress.text.toString()
aapsLogger.debug(TAG, "Removing device as selected: $deviceAddress")
if (devicesMap.containsKey(deviceAddress)) {
val bluetoothDevice = devicesMap[deviceAddress]
aapsLogger.debug(TAG, "Device can be detected near, so trying to remove bond if possible.")
bleSelector.removeDevice(bluetoothDevice!!)
} else {
val remoteDevice = bluetoothAdapter!!.getRemoteDevice(deviceAddress)
if (remoteDevice != null) {
bleSelector.removeDevice(remoteDevice)
}
}
bleSelector.cleanupAfterDeviceRemoved()
updateCurrentlySelectedBTDevice()
})
}
}
private fun updateCurrentlySelectedBTDevice() {
val address = bleSelector.currentlySelectedPumpAddress()
if (StringUtils.isEmpty(address)) {
binding.pumpBleConfigCurrentlySelectedPumpName.text = bleSelector.getText(PumpBLESelectorText.NO_SELECTED_PUMP)
binding.pumpBleConfigCurrentlySelectedPumpAddress.visibility = View.GONE
binding.pumpBleConfigButtonRemove.visibility = View.GONE
} else {
binding.pumpBleConfigCurrentlySelectedPumpAddress.visibility = View.VISIBLE
binding.pumpBleConfigButtonRemove.visibility = View.VISIBLE
binding.pumpBleConfigCurrentlySelectedPumpName.text = bleSelector.currentlySelectedPumpName()
binding.pumpBleConfigCurrentlySelectedPumpAddress.text = address
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean =
when (item.itemId) {
android.R.id.home -> {
finish()
true
}
else -> false
}
override fun onResume() {
super.onResume()
bleSelector.onResume()
prepareForScanning()
updateCurrentlySelectedBTDevice()
}
override fun onDestroy() {
super.onDestroy()
if (scanning) {
stopLeDeviceScan(false)
}
bleSelector.onDestroy()
}
private fun prepareForScanning() {
bleScanner = bluetoothAdapter!!.bluetoothLeScanner
settings = bleSelector.getScanSettings()
filters = bleSelector.getScanFilters()
}
private val bleScanCallback: ScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, scanRecord: ScanResult) {
aapsLogger.debug(TAG, scanRecord.toString())
runOnUiThread { if (addDevice(scanRecord)) deviceListAdapter.notifyDataSetChanged() }
}
override fun onBatchScanResults(results: List<ScanResult>) {
runOnUiThread {
var added = false
for (result in results) {
aapsLogger.debug(TAG, "SCAN: " + result.advertisingSid + " name=" + result.device.address)
if (addDevice(result)) added = true
}
if (added)
deviceListAdapter.notifyDataSetChanged()
}
}
private fun addDevice(result: ScanResult): Boolean {
var device = result.device
device = bleSelector.filterDevice(device)
if (device == null) {
return false
}
deviceListAdapter.addDevice(result)
if (!devicesMap.containsKey(device.address)) {
devicesMap[device.address] = device
}
return true
}
override fun onScanFailed(errorCode: Int) {
aapsLogger.error(TAG, "Scan Failed - Error Code: $errorCode")
bleSelector.onScanFailed(this@PumpBLEConfigActivity, errorCode)
}
}
private fun startLeDeviceScan() {
if (bleScanner == null) {
aapsLogger.error(LTag.PUMPBTCOMM, "startLeDeviceScan failed: bleScanner is null")
return
}
deviceListAdapter.clear()
deviceListAdapter.notifyDataSetChanged()
handler.postDelayed(stopScanAfterTimeoutRunnable, SCAN_PERIOD_MILLIS)
runOnUiThread {
binding.pumpBleConfigScanStart.isEnabled = false
binding.pumpBleConfigButtonScanStop.visibility = View.VISIBLE
}
scanning = true
bleScanner!!.startScan(filters, settings, bleScanCallback)
aapsLogger.debug(LTag.PUMPBTCOMM, "startLeDeviceScan: Scanning Start")
bleSelector.onStartLeDeviceScan(this@PumpBLEConfigActivity)
}
private fun stopLeDeviceScan(manualStop: Boolean) {
if (scanning) {
scanning = false
bleScanner!!.stopScan(bleScanCallback)
aapsLogger.debug(LTag.PUMPBTCOMM, "stopLeDeviceScan: Scanning Stop")
bleSelector.onStopLeDeviceScan(this@PumpBLEConfigActivity)
handler.removeCallbacks(stopScanAfterTimeoutRunnable)
}
if (manualStop) {
bleSelector.onManualStopLeDeviceScan(this@PumpBLEConfigActivity)
} else {
bleSelector.onNonManualStopLeDeviceScan(this@PumpBLEConfigActivity)
}
runOnUiThread {
binding.pumpBleConfigScanStart.isEnabled = true
binding.pumpBleConfigButtonScanStop.visibility = View.GONE
}
}
private inner class LeDeviceListAdapter : BaseAdapter() {
private var devicesList: ArrayList<BluetoothDevice> = arrayListOf()
private var devicesMap: MutableMap<BluetoothDevice, Int> = mutableMapOf()
fun addDevice(result: ScanResult) {
if (!devicesList.contains(result.device)) {
devicesList.add(result.device)
}
devicesMap[result.device] = result.rssi
notifyDataSetChanged()
}
fun clear() {
devicesList.clear()
devicesMap.clear()
notifyDataSetChanged()
}
override fun getCount(): Int {
val c = devicesList.size
aapsLogger.info(TAG, "D: count=$c")
return c
}
override fun getItem(i: Int): Any = devicesList[i]
override fun getItemId(i: Int): Long = i.toLong()
override fun getView(i: Int, convertView: View?, viewGroup: ViewGroup?): View {
var v = convertView
val holder: ViewHolder
if (v == null) {
v = View.inflate(applicationContext, R.layout.pump_ble_config_scan_item, null)
holder = ViewHolder()
holder.deviceAddress = v.findViewById(R.id.pump_ble_config_scan_item_device_address)
holder.deviceName = v.findViewById(R.id.pump_ble_config_scan_item_device_name)
v.tag = holder
} else {
// reuse view if already exists
holder = v.tag as ViewHolder
}
val device = devicesList[i]
var deviceName = device.name
if (StringUtils.isBlank(deviceName)) {
deviceName = bleSelector.getUnknownPumpName()
}
deviceName += " [" + devicesMap[device] + "]"
val currentlySelectedAddress = bleSelector.currentlySelectedPumpAddress() // TODO
if (currentlySelectedAddress == device.address) {
deviceName += " (" + resources.getString(R.string.ble_config_scan_selected) + ")"
}
holder.deviceName!!.text = deviceName
holder.deviceAddress!!.text = device.address
return v!!
}
}
internal class ViewHolder {
var deviceName: TextView? = null
var deviceAddress: TextView? = null
}
companion object {
private val TAG = LTag.PUMPBTCOMM
private const val SCAN_PERIOD_MILLIS: Long = 15000
}
}

View file

@ -0,0 +1,217 @@
package info.nightscout.androidaps.plugins.pump.common.ui
import android.content.Context
import android.os.Bundle
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.plugins.pump.common.R
import info.nightscout.androidaps.plugins.pump.common.databinding.PumpHistoryActivityBinding
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup
import info.nightscout.androidaps.plugins.pump.common.driver.PumpDriverConfigurationCapable
import info.nightscout.androidaps.plugins.pump.common.driver.history.PumpHistoryDataProvider
import info.nightscout.androidaps.plugins.pump.common.driver.history.PumpHistoryEntry
import info.nightscout.androidaps.plugins.pump.common.driver.history.PumpHistoryText
import info.nightscout.androidaps.interfaces.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import javax.inject.Inject
class PumpHistoryActivity : DaggerAppCompatActivity() {
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var context: Context
var filteredHistoryList: MutableList<PumpHistoryEntry> = mutableListOf()
var typeListFull: List<TypeList>? = null
var fullList: MutableList<PumpHistoryEntry> = mutableListOf()
private lateinit var historyDataProvider: PumpHistoryDataProvider
private lateinit var binding: PumpHistoryActivityBinding
var manualChange = false
lateinit var recyclerViewAdapter: RecyclerViewAdapter
private fun prepareData() {
val allData = historyDataProvider.getInitialData()
aapsLogger.info(LTag.PUMP, "Loaded ${allData.size} items from database. [initialSize=${historyDataProvider.getInitialPeriod()}]")
this.fullList.addAll(allData)
}
private fun filterHistory(group: PumpHistoryEntryGroup) {
filteredHistoryList.clear()
if (group === PumpHistoryEntryGroup.All) {
filteredHistoryList.addAll(fullList)
} else {
for (pumpHistoryEntry in fullList) {
if (historyDataProvider.isItemInSelection(pumpHistoryEntry.getEntryTypeGroup(), group)) {
filteredHistoryList.add(pumpHistoryEntry)
}
}
}
aapsLogger.info(LTag.PUMP, "Filtered list ${filteredHistoryList.size} items (group ${group}), from full list (${fullList.size}).")
recyclerViewAdapter.setHistoryListInternal(filteredHistoryList)
recyclerViewAdapter.notifyDataSetChanged()
}
override fun onResume() {
super.onResume()
//filterHistory(selectedGroup)
//setHistoryTypeSpinner()
//aapsLogger.info(LTag.PUMP, "onResume")
//binding.pumpHistoryRoot.requestLayout()
}
private fun setHistoryTypeSpinner() {
manualChange = true
for (i in typeListFull!!.indices) {
if (typeListFull!![i].entryGroup === selectedGroup) {
binding.pumpHistoryType.setSelection(i)
break
}
}
SystemClock.sleep(200)
manualChange = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PumpHistoryActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
// Configuration
val activePump = activePlugin.activePump
if (activePump is PumpDriverConfigurationCapable) {
historyDataProvider = activePump.getPumpDriverConfiguration().getPumpHistoryDataProvider()
} else {
throw RuntimeException("PumpHistoryActivity can be used only with PumpDriverConfigurationCapable pump driver.")
}
prepareData()
binding.pumpHistoryRecyclerView.setHasFixedSize(true)
binding.pumpHistoryRecyclerView.layoutManager = LinearLayoutManager(this)
recyclerViewAdapter = RecyclerViewAdapter(filteredHistoryList)
binding.pumpHistoryRecyclerView.adapter = recyclerViewAdapter
binding.pumpHistoryStatus.visibility = View.GONE
typeListFull = getTypeList(historyDataProvider.getAllowedPumpHistoryGroups())
val spinnerAdapter = ArrayAdapter(this, R.layout.spinner_centered, typeListFull!!)
binding.pumpHistoryText.text = historyDataProvider.getText(PumpHistoryText.PUMP_HISTORY)
binding.pumpHistoryType.adapter = spinnerAdapter
binding.pumpHistoryType.getLayoutParams().width = fromDpToSize(historyDataProvider.getSpinnerWidthInPixels())
binding.pumpHistoryType.requestLayout();
binding.pumpHistoryType.setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
if (manualChange) return
val selected = binding.pumpHistoryType.getSelectedItem() as TypeList
showingType = selected
selectedGroup = selected.entryGroup
filterHistory(selectedGroup)
val selectedText = parent!!.getChildAt(0) as TextView
selectedText.textSize = 15.0f // FIXME hack for selected item, also concerns pump_type marginTop
binding.pumpHistoryTop.requestLayout()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
if (manualChange) return
filterHistory(PumpHistoryEntryGroup.All)
}
})
binding.pumpHistoryTypeText.requestLayout()
}
private fun getTypeList(list: List<PumpHistoryEntryGroup>?): List<TypeList> {
val typeList = ArrayList<TypeList>()
for (pumpHistoryEntryGroup in list!!) {
typeList.add(TypeList(pumpHistoryEntryGroup))
}
return typeList
}
fun fromDpToSize(dpSize: Int): Int {
val scale = context.resources.displayMetrics.density
val pixelsFl = ((dpSize * scale) + 0.5f)
return pixelsFl.toInt()
}
class TypeList internal constructor(var entryGroup: PumpHistoryEntryGroup) {
var name: String
override fun toString(): String {
return name
}
init {
name = entryGroup.translated!!
}
}
class RecyclerViewAdapter internal constructor(
var historyList: List<PumpHistoryEntry>
) : RecyclerView.Adapter<RecyclerViewAdapter.HistoryViewHolder>() {
fun setHistoryListInternal(historyList: List<PumpHistoryEntry>) {
this.historyList = historyList
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): HistoryViewHolder {
val v = LayoutInflater.from(viewGroup.context).inflate(
R.layout.pump_history_item, //
viewGroup, false
)
return HistoryViewHolder(v)
}
override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
val record = historyList[position]
holder.timeView.text = record.getEntryDateTime()
holder.typeView.text = record.getEntryType()
holder.valueView.text = record.getEntryValue()
}
override fun getItemCount(): Int {
return historyList.size
}
class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var timeView: TextView
var typeView: TextView
var valueView: TextView
init {
timeView = itemView.findViewById(R.id.pump_history_time)
typeView = itemView.findViewById(R.id.pump_history_source)
valueView = itemView.findViewById(R.id.pump_history_description)
}
}
}
companion object {
var showingType: TypeList? = null
var selectedGroup = PumpHistoryEntryGroup.All
}
}

View file

@ -0,0 +1,82 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp">
<!-- Currently selected Pump -->
<TextView
style="@style/TextAppearance.AppCompat.Title"
android:id="@+id/pump_ble_config_currently_selected_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/pump_ble_config_currently_selected_pump_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:textSize="16sp" />
<TextView
android:id="@+id/pump_ble_config_currently_selected_pump_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:textSize="12sp" />
</LinearLayout>
<Button
android:id="@+id/pump_ble_config_button_remove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ble_config_remove"
android:visibility="gone" />
</LinearLayout>
<!-- Scan -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="@style/TextAppearance.AppCompat.Title"
android:id="@+id/pump_ble_config_scan_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Button
android:id="@+id/pump_ble_config_scan_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ble_config_button_scan_start" />
<Button
android:id="@+id/pump_ble_config_button_scan_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ble_config_scan_stop"
android:visibility="gone" />
</LinearLayout>
<!-- Scan results -->
<ListView
android:id="@+id/pump_ble_config_scan_device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/pump_ble_config_scan_item_device_name"
style="@style/TextAppearance.AppCompat.Medium.Inverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp" />
<TextView
android:id="@+id/pump_ble_config_scan_item_device_address"
style="@style/TextAppearance.AppCompat.Medium.Inverse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp" />
</LinearLayout>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pump_history_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="?attr/activity_title_backgroundColor"
tools:context=".ui.PumpHistoryActivity">
<LinearLayout
android:id="@+id/pump_history_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="horizontal">
<TextView
android:id="@+id/pump_history_type_text"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="1dp"
android:layout_marginTop="7dp"
android:gravity="center_vertical"
android:text="@string/pump_history_type"
android:textAppearance="?android:attr/textAppearanceSmall" />
<Spinner
android:theme="@style/YourSpinnerItemStyle"
android:id="@+id/pump_history_type"
android:layout_width="100dp"
android:textSize="13sp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp" />
<TextView
android:id="@+id/pump_history_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginEnd="20dp"
android:layout_weight="1"
android:background="@drawable/pillborder"
android:gravity="center_horizontal" />
</LinearLayout>
<TextView
android:id="@+id/pump_history_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pump_history_top"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/pump_history_recycler_view"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="@+id/pump_history_status" />
</RelativeLayout>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="40dp"
android:paddingStart="10dp"
android:paddingEnd="10dp">
<TextView
android:id="@+id/pump_history_time"
android:layout_width="70dp"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textSize="12sp" />
<TextView
android:id="@+id/pump_history_source"
android:layout_width="100dp"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textSize="12sp" />
<TextView
android:id="@+id/pump_history_description"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingStart="5dp"
android:textSize="12sp" />
</LinearLayout>

View file

@ -6,7 +6,10 @@
<string name="pump_operation_not_yet_supported_by_pump">Operation not YET supported by pump.</string> <string name="pump_operation_not_yet_supported_by_pump">Operation not YET supported by pump.</string>
<string name="common_resultok">OK</string> <string name="common_resultok">OK</string>
<!-- PumoCommon - Pump Status --> <string name="pump_serial_number">Pump Serial Number</string>
<!-- PumpCommon - Pump Status -->
<string name="pump_status_never_contacted">Never contacted</string> <string name="pump_status_never_contacted">Never contacted</string>
<string name="pump_status_waking_up">Waking up</string> <string name="pump_status_waking_up">Waking up</string>
<string name="pump_status_error_comm">Error with communication</string> <string name="pump_status_error_comm">Error with communication</string>
@ -15,6 +18,13 @@
<string name="pump_status_invalid_config">Invalid configuration</string> <string name="pump_status_invalid_config">Invalid configuration</string>
<string name="pump_status_active">Active</string> <string name="pump_status_active">Active</string>
<string name="pump_status_sleeping">Sleeping</string> <string name="pump_status_sleeping">Sleeping</string>
<string name="pump_status_not_initialized">Not initialized</string>
<string name="pump_status_initialized">Initialized</string>
<string name="pump_status_encrypt">Encrypting communication</string>
<string name="pump_status_ready">Ready</string>
<string name="pump_status_busy">Busy</string>
<string name="pump_status_suspended">Suspended</string>
<string name="pump_status_executing_command">Executing Command</string>
<!-- PumpCommon - History Group --> <!-- PumpCommon - History Group -->
<string name="history_group_basal">Basals</string> <string name="history_group_basal">Basals</string>
@ -27,5 +37,67 @@
<string name="history_group_prime">Prime</string> <string name="history_group_prime">Prime</string>
<string name="history_group_alarm">Alarms</string> <string name="history_group_alarm">Alarms</string>
<string name="history_group_glucose">Glucose</string> <string name="history_group_glucose">Glucose</string>
<string name="history_group_base">Base</string>
<string name="history_group_other">Other</string>
<string name="history_group_events">All Events</string>
<string name="history_group_events_no_stat">Events (no Stat)</string>
<!-- Time -->
<string name="time_today">Today</string>
<string name="time_last_hour">Last Hour</string>
<string name="time_last_3_hours">Last 3 hours</string>
<string name="time_last_6_hours">Last 6 hours</string>
<string name="time_last_12_hours">Last 12 hours</string>
<string name="time_last_2_days">Last 2 days</string>
<string name="time_last_4_days">Last 4 days</string>
<string name="time_last_week">Last week</string>
<string name="time_last_month">Last month</string>
<!-- BLE Config -->
<string name="ble_config_button_scan_start">Scan</string>
<string name="ble_config_scan_stop">Stop</string>
<string name="ble_config_scan_selected">Selected</string>
<string name="ble_config_scan_scanning">Scanning</string>
<string name="ble_config_scan_finished">Scanning finished</string>
<string name="ble_config_scan_error">Scanning error: %1$d</string>
<string name="ble_config_connected_never">Never</string>
<string name="ble_config_remove">Remove</string>
<!-- BLE Errors -->
<string name="ble_error_bt_disabled">Bluetooth disabled</string>
<string name="ble_error_no_bt_adapter">No Bluetooth Adapter</string>
<string name="ble_error_configured_pump_not_found">Configured Pump Not Found</string>
<string name="ble_error_pump_unreachable">Pump unreachable</string>
<string name="ble_error_failed_to_conn_to_ble_device">Failed To Connect To BLE Device</string>
<string name="ble_error_encryption_failed">Encryption Failed</string>
<string name="ble_error_pump_found_unbonded">Found not Bonded Pump</string>
<!-- Pump Error -->
<string name="pump_settings_error_basal_profiles_not_enabled">Basal profiles/patterns setting is not enabled on pump. Enable it on the pump.</string>
<string name="pump_settings_error_incorrect_basal_profile_selected">Basal profile set on pump is incorrect (must be %s).</string>
<string name="pump_settings_error_wrong_tbr_type_set">Wrong TBR type set on pump (must be %s).</string>
<string name="pump_settings_error_wrong_max_bolus_set">Wrong Max Bolus set on Pump (must be %1$.2f).</string>
<string name="pump_settings_error_wrong_max_basal_set">Wrong Max Basal set on Pump (must be %1$.2f).</string>
<!-- Pump History -->
<string name="pump_history_type">Type:</string>
<plurals name="duration_days">
<item quantity="one">%1$d day</item>
<item quantity="other">%1$d days</item>
</plurals>
<plurals name="duration_hours">
<item quantity="one">%1$d hour</item>
<item quantity="other">%1$d hours</item>
</plurals>
<plurals name="hoursago">
<item quantity="one">%1$d hour ago</item>
<item quantity="other">%1$d hours ago</item>
</plurals>
<plurals name="daysago">
<item quantity="one">%1$d day ago</item>
<item quantity="other">%1$d days ago</item>
</plurals>
</resources> </resources>

View file

@ -0,0 +1,18 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:spinnerItemStyle">@style/YourSpinnerItemStyle</item>
</style>
<style name="YourSpinnerItemStyle" parent="Widget.AppCompat.TextView.SpinnerItem">
<item name="android:textColor">@android:color/white</item>
<item name="android:textSize">15sp</item>
</style>
<style name="mySpinnerItemStyle" parent="ThemeOverlay.AppCompat.Dark">
<item name="android:textSize">15sp</item>
<item name="android:textColor">@color/white</item>
</style>
</resources>