Merge branch 'dev' of https://github.com/nightscout/AndroidAPS into dev
This commit is contained in:
commit
8db5b808ac
173 changed files with 9686 additions and 13220 deletions
|
@ -111,7 +111,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionCode 1500
|
versionCode 1500
|
||||||
version "2.8.2.1-dev-e4"
|
version "2.8.2.1-dev-e5"
|
||||||
buildConfigField "String", "VERSION", '"' + version + '"'
|
buildConfigField "String", "VERSION", '"' + version + '"'
|
||||||
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
|
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
|
||||||
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
|
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
|
||||||
|
@ -186,6 +186,7 @@ dependencies {
|
||||||
implementation project(':danars')
|
implementation project(':danars')
|
||||||
implementation project(':danar')
|
implementation project(':danar')
|
||||||
implementation project(':insight')
|
implementation project(':insight')
|
||||||
|
implementation project(':pump-common')
|
||||||
implementation project(':rileylink')
|
implementation project(':rileylink')
|
||||||
implementation project(':medtronic')
|
implementation project(':medtronic')
|
||||||
implementation project(':omnipod-common')
|
implementation project(':omnipod-common')
|
||||||
|
@ -197,8 +198,6 @@ dependencies {
|
||||||
/* Dagger2 - We are going to use dagger.android which includes
|
/* Dagger2 - We are going to use dagger.android which includes
|
||||||
* support for Activity and fragment injection so we need to include
|
* support for Activity and fragment injection so we need to include
|
||||||
* the following dependencies */
|
* the following dependencies */
|
||||||
|
|
||||||
|
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
|
||||||
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
|
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
|
||||||
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
|
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
|
||||||
|
|
|
@ -8,13 +8,14 @@ import info.nightscout.androidaps.MainApp
|
||||||
import info.nightscout.androidaps.automation.di.AutomationModule
|
import info.nightscout.androidaps.automation.di.AutomationModule
|
||||||
import info.nightscout.androidaps.combo.di.ComboModule
|
import info.nightscout.androidaps.combo.di.ComboModule
|
||||||
import info.nightscout.androidaps.dana.di.DanaHistoryModule
|
import info.nightscout.androidaps.dana.di.DanaHistoryModule
|
||||||
import info.nightscout.androidaps.di.CoreModule
|
|
||||||
import info.nightscout.androidaps.dana.di.DanaModule
|
import info.nightscout.androidaps.dana.di.DanaModule
|
||||||
import info.nightscout.androidaps.danar.di.DanaRModule
|
import info.nightscout.androidaps.danar.di.DanaRModule
|
||||||
import info.nightscout.androidaps.danars.di.DanaRSModule
|
import info.nightscout.androidaps.danars.di.DanaRSModule
|
||||||
import info.nightscout.androidaps.database.DatabaseModule
|
import info.nightscout.androidaps.database.DatabaseModule
|
||||||
|
import info.nightscout.androidaps.di.CoreModule
|
||||||
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
|
import info.nightscout.androidaps.insight.di.InsightDatabaseModule
|
||||||
import info.nightscout.androidaps.insight.di.InsightModule
|
import info.nightscout.androidaps.insight.di.InsightModule
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.di.PumpCommonModule
|
||||||
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
|
import info.nightscout.androidaps.plugins.pump.common.di.RileyLinkModule
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
|
import info.nightscout.androidaps.plugins.pump.medtronic.di.MedtronicModule
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.eros.dagger.OmnipodErosModule
|
import info.nightscout.androidaps.plugins.pump.omnipod.eros.dagger.OmnipodErosModule
|
||||||
|
@ -36,6 +37,7 @@ import javax.inject.Singleton
|
||||||
CommandQueueModule::class,
|
CommandQueueModule::class,
|
||||||
ObjectivesModule::class,
|
ObjectivesModule::class,
|
||||||
WizardModule::class,
|
WizardModule::class,
|
||||||
|
PumpCommonModule::class,
|
||||||
RileyLinkModule::class,
|
RileyLinkModule::class,
|
||||||
MedtronicModule::class,
|
MedtronicModule::class,
|
||||||
OmnipodErosModule::class,
|
OmnipodErosModule::class,
|
||||||
|
|
|
@ -72,13 +72,17 @@ open class AppModule {
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface AppBindings {
|
interface AppBindings {
|
||||||
|
|
||||||
@Binds fun bindContext(mainApp: MainApp): Context
|
@Binds fun bindContext(mainApp: MainApp): Context
|
||||||
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
|
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
|
||||||
@Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin
|
@Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin
|
||||||
@Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider
|
@Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider
|
||||||
@Binds fun bindConfigInterface(config: ConfigImpl): Config
|
@Binds fun bindConfigInterface(config: ConfigImpl): Config
|
||||||
@Binds fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder
|
|
||||||
|
@Binds
|
||||||
|
fun bindConfigBuilderInterface(configBuilderPlugin: ConfigBuilderPlugin): ConfigBuilder
|
||||||
@Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface
|
@Binds fun bindTreatmentsInterface(treatmentsPlugin: TreatmentsPlugin): TreatmentsInterface
|
||||||
|
|
||||||
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
|
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
|
||||||
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
|
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
|
||||||
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
|
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
|
||||||
|
@ -87,6 +91,7 @@ open class AppModule {
|
||||||
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
|
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
|
||||||
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
|
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
|
||||||
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
|
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
|
||||||
|
|
||||||
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
|
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,4 +184,4 @@ class PluginStore @Inject constructor(
|
||||||
|
|
||||||
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
|
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package info.nightscout.androidaps.db
|
|
||||||
|
|
||||||
interface DbObjectBase {
|
|
||||||
|
|
||||||
val date: Long
|
|
||||||
val pumpId: Long
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
||||||
* Created by andy on 30.11.2019.
|
* Created by andy on 30.11.2019.
|
||||||
*/
|
*/
|
||||||
@DatabaseTable(tableName = "PodHistory")
|
@DatabaseTable(tableName = "PodHistory")
|
||||||
public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHistoryRecord> {
|
public class OmnipodHistoryRecord implements Comparable<OmnipodHistoryRecord> {
|
||||||
|
|
||||||
@DatabaseField(id = true)
|
@DatabaseField(id = true)
|
||||||
public long date;
|
public long date;
|
||||||
|
@ -42,7 +42,6 @@ public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHis
|
||||||
generatePumpId();
|
generatePumpId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDate() {
|
public long getDate() {
|
||||||
return this.date;
|
return this.date;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +90,6 @@ public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHis
|
||||||
this.successConfirmed = successConfirmed;
|
this.successConfirmed = successConfirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getPumpId() {
|
public long getPumpId() {
|
||||||
return pumpId;
|
return pumpId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@DatabaseTable(tableName = "TemporaryBasals")
|
@DatabaseTable(tableName = "TemporaryBasals")
|
||||||
public class TemporaryBasal implements Interval, DbObjectBase {
|
public class TemporaryBasal implements Interval {
|
||||||
|
|
||||||
@Inject public AAPSLogger aapsLogger;
|
@Inject public AAPSLogger aapsLogger;
|
||||||
@Inject public ProfileFunction profileFunction;
|
@Inject public ProfileFunction profileFunction;
|
||||||
|
@ -360,12 +360,10 @@ public class TemporaryBasal implements Interval, DbObjectBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDate() {
|
public long getDate() {
|
||||||
return this.date;
|
return this.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getPumpId() {
|
public long getPumpId() {
|
||||||
return this.pumpId;
|
return this.pumpId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ import javax.inject.Inject;
|
||||||
import dagger.android.HasAndroidInjector;
|
import dagger.android.HasAndroidInjector;
|
||||||
import info.nightscout.androidaps.Constants;
|
import info.nightscout.androidaps.Constants;
|
||||||
import info.nightscout.androidaps.core.R;
|
import info.nightscout.androidaps.core.R;
|
||||||
import info.nightscout.androidaps.interfaces.Profile;
|
|
||||||
import info.nightscout.androidaps.database.entities.Bolus;
|
import info.nightscout.androidaps.database.entities.Bolus;
|
||||||
import info.nightscout.androidaps.interfaces.ActivePlugin;
|
import info.nightscout.androidaps.interfaces.ActivePlugin;
|
||||||
import info.nightscout.androidaps.interfaces.Insulin;
|
import info.nightscout.androidaps.interfaces.Insulin;
|
||||||
|
import info.nightscout.androidaps.interfaces.Profile;
|
||||||
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
||||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface;
|
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface;
|
||||||
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
|
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
|
||||||
|
@ -33,7 +33,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@DatabaseTable(tableName = Treatment.TABLE_TREATMENTS)
|
@DatabaseTable(tableName = Treatment.TABLE_TREATMENTS)
|
||||||
public class Treatment implements DataPointWithLabelInterface, DbObjectBase {
|
public class Treatment implements DataPointWithLabelInterface {
|
||||||
@Inject public DefaultValueHelper defaultValueHelper;
|
@Inject public DefaultValueHelper defaultValueHelper;
|
||||||
@Inject public ResourceHelper resourceHelper;
|
@Inject public ResourceHelper resourceHelper;
|
||||||
@Inject public ProfileFunction profileFunction;
|
@Inject public ProfileFunction profileFunction;
|
||||||
|
@ -270,12 +270,10 @@ public class Treatment implements DataPointWithLabelInterface, DbObjectBase {
|
||||||
|
|
||||||
// ----------------- DataPointInterface end --------------------
|
// ----------------- DataPointInterface end --------------------
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDate() {
|
public long getDate() {
|
||||||
return this.date;
|
return this.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getPumpId() {
|
public long getPumpId() {
|
||||||
return this.pumpId;
|
return this.pumpId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
package info.nightscout.androidaps.plugins.general.actions.defs
|
package info.nightscout.androidaps.plugins.general.actions.defs
|
||||||
|
|
||||||
interface CustomActionType {
|
interface CustomActionType {
|
||||||
val key: String?
|
fun getKey(): String
|
||||||
}
|
}
|
|
@ -320,32 +320,6 @@
|
||||||
<string name="timedetection">Time detection</string>
|
<string name="timedetection">Time detection</string>
|
||||||
<string name="format_hour_minute">%1$dh %2$dm</string>
|
<string name="format_hour_minute">%1$dh %2$dm</string>
|
||||||
|
|
||||||
<!-- PumoCommon - Pump Abstract -->
|
|
||||||
<string name="pump_operation_not_supported_by_pump_driver">Operation not supported by pump and/or driver.</string>
|
|
||||||
<string name="pump_operation_not_yet_supported_by_pump">Operation not YET supported by pump.</string>
|
|
||||||
<string name="common_resultok">OK</string>
|
|
||||||
|
|
||||||
<!-- PumoCommon - Pump Status -->
|
|
||||||
<string name="pump_status_never_contacted">Never contacted</string>
|
|
||||||
<string name="pump_status_waking_up">Waking up</string>
|
|
||||||
<string name="pump_status_error_comm">Error with communication</string>
|
|
||||||
<string name="pump_status_timeout_comm">Timeout on communication</string>
|
|
||||||
<string name="pump_status_pump_unreachable">Pump unreachable</string>
|
|
||||||
<string name="pump_status_invalid_config">Invalid configuration</string>
|
|
||||||
<string name="pump_status_active">Active</string>
|
|
||||||
<string name="pump_status_sleeping">Sleeping</string>
|
|
||||||
|
|
||||||
<!-- PumpCommon - History Group -->
|
|
||||||
<string name="history_group_basal">Basals</string>
|
|
||||||
<string name="history_group_configuration">Configurations</string>
|
|
||||||
<string name="history_group_notification">Notifications</string>
|
|
||||||
<string name="history_group_statistic">Statistics</string>
|
|
||||||
<string name="history_group_unknown">Unknowns</string>
|
|
||||||
<string name="history_group_all">All</string>
|
|
||||||
<string name="history_group_bolus">Boluses</string>
|
|
||||||
<string name="history_group_prime">Prime</string>
|
|
||||||
<string name="history_group_alarm">Alarms</string>
|
|
||||||
<string name="history_group_glucose">Glucose</string>
|
|
||||||
<string name="mute5min">Mute for 5 minutes</string>
|
<string name="mute5min">Mute for 5 minutes</string>
|
||||||
|
|
||||||
<!-- Maintenance -->
|
<!-- Maintenance -->
|
||||||
|
|
|
@ -18,6 +18,7 @@ dependencies {
|
||||||
androidTestImplementation "androidx.test:rules:$androidx_rules"
|
androidTestImplementation "androidx.test:rules:$androidx_rules"
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||||
|
testImplementation 'org.mockito:mockito-inline:2.8.47'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
@ -16,5 +16,6 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':core')
|
implementation project(':core')
|
||||||
|
implementation project(':pump-common')
|
||||||
implementation project(':rileylink')
|
implementation project(':rileylink')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,476 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.common;
|
|
||||||
|
|
||||||
import static info.nightscout.androidaps.extensions.PumpStateExtensionKt.convertedToAbsolute;
|
|
||||||
import static info.nightscout.androidaps.extensions.PumpStateExtensionKt.getPlannedRemainingMinutes;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import dagger.android.HasAndroidInjector;
|
|
||||||
import info.nightscout.androidaps.core.R;
|
|
||||||
import info.nightscout.androidaps.data.DetailedBolusInfo;
|
|
||||||
import info.nightscout.androidaps.interfaces.Profile;
|
|
||||||
import info.nightscout.androidaps.data.PumpEnactResult;
|
|
||||||
import info.nightscout.androidaps.events.EventAppExit;
|
|
||||||
import info.nightscout.androidaps.events.EventCustomActionsChanged;
|
|
||||||
import info.nightscout.androidaps.extensions.PumpStateExtensionKt;
|
|
||||||
import info.nightscout.androidaps.interfaces.ActivePlugin;
|
|
||||||
import info.nightscout.androidaps.interfaces.CommandQueueProvider;
|
|
||||||
import info.nightscout.androidaps.interfaces.Constraints;
|
|
||||||
import info.nightscout.androidaps.interfaces.PluginDescription;
|
|
||||||
import info.nightscout.androidaps.interfaces.PumpDescription;
|
|
||||||
import info.nightscout.androidaps.interfaces.Pump;
|
|
||||||
import info.nightscout.androidaps.interfaces.PumpPluginBase;
|
|
||||||
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.common.ManufacturerType;
|
|
||||||
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
|
||||||
import info.nightscout.androidaps.utils.DateUtil;
|
|
||||||
import info.nightscout.androidaps.utils.DecimalFormatter;
|
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
|
||||||
import info.nightscout.androidaps.utils.rx.AapsSchedulers;
|
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 23.04.18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// When using this class, make sure that your first step is to create mConnection (see MedtronicPumpPlugin)
|
|
||||||
|
|
||||||
public abstract class PumpPluginAbstract extends PumpPluginBase implements Pump, Constraints {
|
|
||||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
protected HasAndroidInjector injector;
|
|
||||||
protected AAPSLogger aapsLogger;
|
|
||||||
protected RxBusWrapper rxBus;
|
|
||||||
protected ActivePlugin activePlugin;
|
|
||||||
protected Context context;
|
|
||||||
protected FabricPrivacy fabricPrivacy;
|
|
||||||
protected ResourceHelper resourceHelper;
|
|
||||||
protected CommandQueueProvider commandQueue;
|
|
||||||
protected SP sp;
|
|
||||||
protected DateUtil dateUtil;
|
|
||||||
protected PumpDescription pumpDescription = new PumpDescription();
|
|
||||||
protected ServiceConnection serviceConnection;
|
|
||||||
protected boolean serviceRunning = false;
|
|
||||||
protected PumpDriverState pumpState = PumpDriverState.NotInitialized;
|
|
||||||
protected boolean displayConnectionMessages = false;
|
|
||||||
protected PumpType pumpType;
|
|
||||||
protected AapsSchedulers aapsSchedulers;
|
|
||||||
protected PumpSync pumpSync;
|
|
||||||
|
|
||||||
protected PumpPluginAbstract(
|
|
||||||
PluginDescription pluginDescription,
|
|
||||||
PumpType pumpType,
|
|
||||||
HasAndroidInjector injector,
|
|
||||||
ResourceHelper resourceHelper,
|
|
||||||
AAPSLogger aapsLogger,
|
|
||||||
CommandQueueProvider commandQueue,
|
|
||||||
RxBusWrapper rxBus,
|
|
||||||
ActivePlugin activePlugin,
|
|
||||||
SP sp,
|
|
||||||
Context context,
|
|
||||||
FabricPrivacy fabricPrivacy,
|
|
||||||
DateUtil dateUtil,
|
|
||||||
AapsSchedulers aapsSchedulers,
|
|
||||||
PumpSync pumpSync
|
|
||||||
) {
|
|
||||||
|
|
||||||
super(pluginDescription, injector, aapsLogger, resourceHelper, commandQueue);
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.rxBus = rxBus;
|
|
||||||
this.activePlugin = activePlugin;
|
|
||||||
this.context = context;
|
|
||||||
this.fabricPrivacy = fabricPrivacy;
|
|
||||||
this.resourceHelper = resourceHelper;
|
|
||||||
this.sp = sp;
|
|
||||||
this.commandQueue = commandQueue;
|
|
||||||
|
|
||||||
pumpDescription.fillFor(pumpType);
|
|
||||||
this.pumpType = pumpType;
|
|
||||||
this.dateUtil = dateUtil;
|
|
||||||
this.aapsSchedulers = aapsSchedulers;
|
|
||||||
this.pumpSync = pumpSync;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract void initPumpStatusData();
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
initPumpStatusData();
|
|
||||||
|
|
||||||
Intent intent = new Intent(context, getServiceClass());
|
|
||||||
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
|
|
||||||
serviceRunning = true;
|
|
||||||
|
|
||||||
disposable.add(rxBus
|
|
||||||
.toObservable(EventAppExit.class)
|
|
||||||
.observeOn(aapsSchedulers.getIo())
|
|
||||||
.subscribe(event -> context.unbindService(serviceConnection), fabricPrivacy::logException)
|
|
||||||
);
|
|
||||||
onStartCustomActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, this.deviceID() + " onStop()");
|
|
||||||
|
|
||||||
context.unbindService(serviceConnection);
|
|
||||||
|
|
||||||
serviceRunning = false;
|
|
||||||
|
|
||||||
disposable.clear();
|
|
||||||
super.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If we need to run any custom actions in onStart (triggering events, etc)
|
|
||||||
*/
|
|
||||||
public abstract void onStartCustomActions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service class (same one you did serviceConnection for)
|
|
||||||
*
|
|
||||||
* @return Class
|
|
||||||
*/
|
|
||||||
public abstract Class getServiceClass();
|
|
||||||
|
|
||||||
public abstract PumpStatus getPumpStatusData();
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isInitialized() {
|
|
||||||
return pumpState.isInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isSuspended() {
|
|
||||||
return pumpState == PumpDriverState.Suspended;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isBusy() {
|
|
||||||
return pumpState == PumpDriverState.Busy;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isConnected() {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "isConnected [PumpPluginAbstract].");
|
|
||||||
return pumpState.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isConnecting() {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "isConnecting [PumpPluginAbstract].");
|
|
||||||
return pumpState == PumpDriverState.Connecting;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void connect(@NonNull String reason) {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "connect (reason={}) [PumpPluginAbstract] - default (empty) implementation." + reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void disconnect(@NonNull String reason) {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "disconnect (reason={}) [PumpPluginAbstract] - default (empty) implementation." + reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void stopConnecting() {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "stopConnecting [PumpPluginAbstract] - default (empty) implementation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isHandshakeInProgress() {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "isHandshakeInProgress [PumpPluginAbstract] - default (empty) implementation.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finishHandshaking() {
|
|
||||||
if (displayConnectionMessages)
|
|
||||||
aapsLogger.debug(LTag.PUMP, "finishHandshaking [PumpPluginAbstract] - default (empty) implementation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload to pump new basal profile
|
|
||||||
@NonNull public PumpEnactResult setNewBasalProfile(@NonNull Profile profile) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "setNewBasalProfile [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isThisProfileSet(@NonNull Profile profile) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "isThisProfileSet [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public long lastDataTime() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "lastDataTime [PumpPluginAbstract].");
|
|
||||||
return getPumpStatusData().lastConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public double getBaseBasalRate() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "getBaseBasalRate [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return 0.0d;
|
|
||||||
} // base basal rate, not temp basal
|
|
||||||
|
|
||||||
|
|
||||||
public void stopBolusDelivering() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "stopBolusDelivering [PumpPluginAbstract] - Not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public PumpEnactResult setTempBasalAbsolute(double absoluteRate, int durationInMinutes, @NonNull Profile profile, boolean enforceNew, @NonNull PumpSync.TemporaryBasalType tbrType) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "setTempBasalAbsolute [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public PumpEnactResult setTempBasalPercent(int percent, int durationInMinutes, @NonNull Profile profile, boolean enforceNew, @NonNull PumpSync.TemporaryBasalType tbrType) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "setTempBasalPercent [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull public PumpEnactResult setExtendedBolus(double insulin, int durationInMinutes) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "setExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// some pumps might set a very short temp close to 100% as cancelling a temp can be noisy
|
|
||||||
// when the cancel request is requested by the user (forced), the pump should always do a real cancel
|
|
||||||
|
|
||||||
@NonNull public PumpEnactResult cancelTempBasal(boolean enforceNew) {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "cancelTempBasal [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull public PumpEnactResult cancelExtendedBolus() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "cancelExtendedBolus [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Status to be passed to NS
|
|
||||||
|
|
||||||
// public JSONObject getJSONStatus(Profile profile, String profileName) {
|
|
||||||
// return pumpDriver.getJSONStatus(profile, profileName);
|
|
||||||
// }
|
|
||||||
|
|
||||||
public String deviceID() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "deviceID [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return "FakeDevice";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Pump capabilities
|
|
||||||
|
|
||||||
@NonNull public PumpDescription getPumpDescription() {
|
|
||||||
return pumpDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Short info for SMS, Wear etc
|
|
||||||
|
|
||||||
public boolean isFakingTempsByExtendedBoluses() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "isFakingTempsByExtendedBoluses [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public PumpEnactResult loadTDDs() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "loadTDDs [PumpPluginAbstract] - Not implemented.");
|
|
||||||
return getOperationNotSupportedWithCustomText(R.string.pump_operation_not_supported_by_pump_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public JSONObject getJSONStatus(@NonNull Profile profile, @NonNull String profileName, @NonNull String version) {
|
|
||||||
|
|
||||||
if ((getPumpStatusData().lastConnection + 60 * 60 * 1000L) < System.currentTimeMillis()) {
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
JSONObject pump = new JSONObject();
|
|
||||||
JSONObject battery = new JSONObject();
|
|
||||||
JSONObject status = new JSONObject();
|
|
||||||
JSONObject extended = new JSONObject();
|
|
||||||
try {
|
|
||||||
battery.put("percent", getPumpStatusData().batteryRemaining);
|
|
||||||
status.put("status", getPumpStatusData().pumpStatusType != null ? getPumpStatusData().pumpStatusType.getStatus() : "normal");
|
|
||||||
extended.put("Version", version);
|
|
||||||
try {
|
|
||||||
extended.put("ActiveProfile", profileName);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PumpSync.PumpState.TemporaryBasal tb = pumpSync.expectedPumpState().getTemporaryBasal();
|
|
||||||
if (tb != null) {
|
|
||||||
extended.put("TempBasalAbsoluteRate", convertedToAbsolute(tb, now, profile));
|
|
||||||
extended.put("TempBasalStart", dateUtil.dateAndTimeString(tb.getTimestamp()));
|
|
||||||
extended.put("TempBasalRemaining", getPlannedRemainingMinutes(tb));
|
|
||||||
}
|
|
||||||
|
|
||||||
PumpSync.PumpState.ExtendedBolus eb = pumpSync.expectedPumpState().getExtendedBolus();
|
|
||||||
if (eb != null) {
|
|
||||||
extended.put("ExtendedBolusAbsoluteRate", eb.getRate());
|
|
||||||
extended.put("ExtendedBolusStart", dateUtil.dateAndTimeString(eb.getTimestamp()));
|
|
||||||
extended.put("ExtendedBolusRemaining", getPlannedRemainingMinutes(eb));
|
|
||||||
}
|
|
||||||
|
|
||||||
status.put("timestamp", dateUtil.toISOString(dateUtil.now()));
|
|
||||||
|
|
||||||
pump.put("battery", battery);
|
|
||||||
pump.put("status", status);
|
|
||||||
pump.put("extended", extended);
|
|
||||||
pump.put("reservoir", getPumpStatusData().reservoirRemainingUnits);
|
|
||||||
pump.put("clock", dateUtil.toISOString(dateUtil.now()));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
aapsLogger.error("Unhandled exception", e);
|
|
||||||
}
|
|
||||||
return pump;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME i18n, null checks: iob, TDD
|
|
||||||
@NonNull @Override
|
|
||||||
public String shortStatus(boolean veryShort) {
|
|
||||||
String ret = "";
|
|
||||||
if (getPumpStatusData().lastConnection != 0) {
|
|
||||||
long agoMsec = System.currentTimeMillis() - getPumpStatusData().lastConnection;
|
|
||||||
int agoMin = (int) (agoMsec / 60d / 1000d);
|
|
||||||
ret += "LastConn: " + agoMin + " min ago\n";
|
|
||||||
}
|
|
||||||
if (getPumpStatusData().lastBolusTime != null && getPumpStatusData().lastBolusTime.getTime() != 0) {
|
|
||||||
ret += "LastBolus: " + DecimalFormatter.INSTANCE.to2Decimal(getPumpStatusData().lastBolusAmount) + "U @" + //
|
|
||||||
android.text.format.DateFormat.format("HH:mm", getPumpStatusData().lastBolusTime) + "\n";
|
|
||||||
}
|
|
||||||
PumpSync.PumpState.TemporaryBasal activeTemp = pumpSync.expectedPumpState().getTemporaryBasal();
|
|
||||||
if (activeTemp != null) {
|
|
||||||
ret += "Temp: " + PumpStateExtensionKt.toStringFull(activeTemp, dateUtil) + "\n";
|
|
||||||
}
|
|
||||||
PumpSync.PumpState.ExtendedBolus activeExtendedBolus = pumpSync.expectedPumpState().getExtendedBolus();
|
|
||||||
if (activeExtendedBolus != null) {
|
|
||||||
ret += "Extended: " + PumpStateExtensionKt.toStringFull(activeExtendedBolus, dateUtil) + "\n";
|
|
||||||
}
|
|
||||||
// if (!veryShort) {
|
|
||||||
// ret += "TDD: " + DecimalFormatter.to0Decimal(pumpStatus.dailyTotalUnits) + " / "
|
|
||||||
// + pumpStatus.maxDailyTotalUnits + " U\n";
|
|
||||||
// }
|
|
||||||
ret += "IOB: " + getPumpStatusData().iob + "U\n";
|
|
||||||
ret += "Reserv: " + DecimalFormatter.INSTANCE.to0Decimal(getPumpStatusData().reservoirRemainingUnits) + "U\n";
|
|
||||||
ret += "Batt: " + getPumpStatusData().batteryRemaining + "\n";
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (detailedBolusInfo.insulin == 0 && detailedBolusInfo.carbs == 0) {
|
|
||||||
// neither carbs nor bolus requested
|
|
||||||
aapsLogger.error("deliverTreatment: Invalid input");
|
|
||||||
return new PumpEnactResult(getInjector()).success(false).enacted(false).bolusDelivered(0d).carbsDelivered(0d)
|
|
||||||
.comment(R.string.invalidinput);
|
|
||||||
} else if (detailedBolusInfo.insulin > 0) {
|
|
||||||
// bolus needed, ask pump to deliver it
|
|
||||||
return deliverBolus(detailedBolusInfo);
|
|
||||||
} else {
|
|
||||||
//if (MedtronicHistoryData.doubleBolusDebug)
|
|
||||||
// aapsLogger.debug("DoubleBolusDebug: deliverTreatment::(carb only entry)");
|
|
||||||
|
|
||||||
// no bolus required, carb only treatment
|
|
||||||
activePlugin.getActiveTreatments().addToHistoryTreatment(detailedBolusInfo, true);
|
|
||||||
|
|
||||||
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE;
|
|
||||||
bolusingEvent.setT(new EventOverviewBolusProgress.Treatment(0, 0, detailedBolusInfo.getBolusType() == DetailedBolusInfo.BolusType.SMB));
|
|
||||||
bolusingEvent.setPercent(100);
|
|
||||||
rxBus.send(bolusingEvent);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "deliverTreatment: Carb only treatment.");
|
|
||||||
|
|
||||||
return new PumpEnactResult(getInjector()).success(true).enacted(true).bolusDelivered(0d)
|
|
||||||
.carbsDelivered(detailedBolusInfo.carbs).comment(R.string.common_resultok);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
triggerUIChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void refreshCustomActionsList() {
|
|
||||||
rxBus.send(new EventCustomActionsChanged());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull public ManufacturerType manufacturer() {
|
|
||||||
return pumpType.getManufacturer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public PumpType model() {
|
|
||||||
return pumpType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpType getPumpType() {
|
|
||||||
return pumpType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setPumpType(PumpType pumpType) {
|
|
||||||
this.pumpType = pumpType;
|
|
||||||
this.pumpDescription.fillFor(pumpType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean canHandleDST() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo);
|
|
||||||
|
|
||||||
protected abstract void triggerUIChange();
|
|
||||||
|
|
||||||
private PumpEnactResult getOperationNotSupportedWithCustomText(int resourceId) {
|
|
||||||
return new PumpEnactResult(getInjector()).success(false).enacted(false).comment(resourceId);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.common.data;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.interfaces.ProfileStore;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
|
||||||
import info.nightscout.androidaps.utils.DateUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 4/28/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class PumpStatus {
|
|
||||||
|
|
||||||
// connection
|
|
||||||
public long lastDataTime;
|
|
||||||
public long lastConnection = 0L;
|
|
||||||
public long previousConnection = 0L; // here should be stored last connection of previous session (so needs to be
|
|
||||||
// read before lastConnection is modified for first time).
|
|
||||||
|
|
||||||
// last bolus
|
|
||||||
public Date lastBolusTime;
|
|
||||||
public Double lastBolusAmount;
|
|
||||||
|
|
||||||
// other pump settings
|
|
||||||
public String activeProfileName = "0";
|
|
||||||
public double reservoirRemainingUnits = 0.0d;
|
|
||||||
public int reservoirFullUnits = 0;
|
|
||||||
public int batteryRemaining = 0; // percent, so 0-100
|
|
||||||
public Double batteryVoltage = null;
|
|
||||||
|
|
||||||
|
|
||||||
// iob
|
|
||||||
public String iob = null;
|
|
||||||
|
|
||||||
// TDD
|
|
||||||
public Double dailyTotalUnits;
|
|
||||||
public String maxDailyTotalUnits;
|
|
||||||
public boolean validBasalRateProfileSelectedOnPump = true;
|
|
||||||
public ProfileStore profileStore;
|
|
||||||
public String units; // Constants.MGDL or Constants.MMOL
|
|
||||||
public PumpStatusType pumpStatusType = PumpStatusType.Running;
|
|
||||||
public Double[] basalsByHour;
|
|
||||||
public double currentBasal = 0;
|
|
||||||
public int tempBasalInProgress = 0;
|
|
||||||
public int tempBasalRatio = 0;
|
|
||||||
public int tempBasalRemainMin = 0;
|
|
||||||
public Date tempBasalStart;
|
|
||||||
public PumpType pumpType;
|
|
||||||
//protected PumpDescription pumpDescription;
|
|
||||||
|
|
||||||
|
|
||||||
public PumpStatus(PumpType pumpType) {
|
|
||||||
// public PumpStatus(PumpDescription pumpDescription) {
|
|
||||||
// this.pumpDescription = pumpDescription;
|
|
||||||
|
|
||||||
// this.initSettings();
|
|
||||||
this.pumpType = pumpType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void initSettings();
|
|
||||||
|
|
||||||
public void setLastCommunicationToNow() {
|
|
||||||
this.lastDataTime = System.currentTimeMillis();
|
|
||||||
this.lastConnection = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String getErrorInfo();
|
|
||||||
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import dagger.android.support.DaggerFragment
|
||||||
import info.nightscout.androidaps.events.EventExtendedBolusChange
|
import info.nightscout.androidaps.events.EventExtendedBolusChange
|
||||||
import info.nightscout.androidaps.events.EventPumpStatusChanged
|
import info.nightscout.androidaps.events.EventPumpStatusChanged
|
||||||
import info.nightscout.androidaps.events.EventTempBasalChange
|
import info.nightscout.androidaps.events.EventTempBasalChange
|
||||||
import info.nightscout.androidaps.extensions.toStringFull
|
|
||||||
import info.nightscout.androidaps.interfaces.ActivePlugin
|
import info.nightscout.androidaps.interfaces.ActivePlugin
|
||||||
import info.nightscout.androidaps.interfaces.CommandQueueProvider
|
import info.nightscout.androidaps.interfaces.CommandQueueProvider
|
||||||
import info.nightscout.androidaps.interfaces.PumpSync
|
import info.nightscout.androidaps.interfaces.PumpSync
|
||||||
|
@ -99,7 +98,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
binding.pumpStatusIcon.text = "{fa-bed}"
|
binding.pumpStatusIcon.text = "{fa-bed}"
|
||||||
|
|
||||||
binding.history.setOnClickListener {
|
binding.history.setOnClickListener {
|
||||||
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
|
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() == true) {
|
||||||
startActivity(Intent(context, MedtronicHistoryActivity::class.java))
|
startActivity(Intent(context, MedtronicHistoryActivity::class.java))
|
||||||
} else {
|
} else {
|
||||||
displayNotConfiguredDialog()
|
displayNotConfiguredDialog()
|
||||||
|
@ -107,7 +106,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.refresh.setOnClickListener {
|
binding.refresh.setOnClickListener {
|
||||||
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() != true) {
|
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() != true) {
|
||||||
displayNotConfiguredDialog()
|
displayNotConfiguredDialog()
|
||||||
} else {
|
} else {
|
||||||
binding.refresh.isEnabled = false
|
binding.refresh.isEnabled = false
|
||||||
|
@ -121,7 +120,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.stats.setOnClickListener {
|
binding.stats.setOnClickListener {
|
||||||
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
|
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() == true) {
|
||||||
startActivity(Intent(context, RileyLinkStatusActivity::class.java))
|
startActivity(Intent(context, RileyLinkStatusActivity::class.java))
|
||||||
} else {
|
} else {
|
||||||
displayNotConfiguredDialog()
|
displayNotConfiguredDialog()
|
||||||
|
@ -161,7 +160,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
.observeOn(aapsSchedulers.main)
|
.observeOn(aapsSchedulers.main)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
aapsLogger.debug(LTag.PUMP, "EventMedtronicPumpConfigurationChanged triggered")
|
aapsLogger.debug(LTag.PUMP, "EventMedtronicPumpConfigurationChanged triggered")
|
||||||
medtronicPumpPlugin.rileyLinkService?.verifyConfiguration()
|
medtronicPumpPlugin.rileyLinkService.verifyConfiguration()
|
||||||
updateGUI()
|
updateGUI()
|
||||||
}, fabricPrivacy::logException)
|
}, fabricPrivacy::logException)
|
||||||
disposable += rxBus
|
disposable += rxBus
|
||||||
|
@ -193,7 +192,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun setDeviceStatus() {
|
private fun setDeviceStatus() {
|
||||||
val resourceId = rileyLinkServiceData.rileyLinkServiceState.resourceId
|
val resourceId = rileyLinkServiceData.rileyLinkServiceState.resourceId
|
||||||
val rileyLinkError = medtronicPumpPlugin.rileyLinkService?.error
|
val rileyLinkError = medtronicPumpPlugin.rileyLinkService.error
|
||||||
binding.rlStatus.text =
|
binding.rlStatus.text =
|
||||||
when {
|
when {
|
||||||
rileyLinkServiceData.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> resourceHelper.gs(resourceId)
|
rileyLinkServiceData.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> resourceHelper.gs(resourceId)
|
||||||
|
@ -210,35 +209,38 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
} ?: "-"
|
} ?: "-"
|
||||||
|
|
||||||
when (medtronicPumpStatus.pumpDeviceState) {
|
when (medtronicPumpStatus.pumpDeviceState) {
|
||||||
null,
|
PumpDeviceState.Sleeping ->
|
||||||
PumpDeviceState.Sleeping -> binding.pumpStatusIcon.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
|
binding.pumpStatusIcon.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
|
||||||
|
|
||||||
PumpDeviceState.NeverContacted,
|
PumpDeviceState.NeverContacted,
|
||||||
PumpDeviceState.WakingUp,
|
PumpDeviceState.WakingUp,
|
||||||
PumpDeviceState.PumpUnreachable,
|
PumpDeviceState.PumpUnreachable,
|
||||||
PumpDeviceState.ErrorWhenCommunicating,
|
PumpDeviceState.ErrorWhenCommunicating,
|
||||||
PumpDeviceState.TimeoutWhenCommunicating,
|
PumpDeviceState.TimeoutWhenCommunicating,
|
||||||
PumpDeviceState.InvalidConfiguration -> binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
|
PumpDeviceState.InvalidConfiguration ->
|
||||||
|
binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
|
||||||
|
|
||||||
PumpDeviceState.Active -> {
|
PumpDeviceState.Active -> {
|
||||||
val cmd = medtronicUtil.currentCommand
|
val cmd = medtronicUtil.getCurrentCommand()
|
||||||
if (cmd == null)
|
if (cmd == null)
|
||||||
binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
|
binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
|
||||||
else {
|
else {
|
||||||
aapsLogger.debug(LTag.PUMP, "Command: $cmd")
|
aapsLogger.debug(LTag.PUMP, "Command: $cmd")
|
||||||
val cmdResourceId = cmd.resourceId
|
val cmdResourceId = cmd.resourceId //!!
|
||||||
if (cmd == MedtronicCommandType.GetHistoryData) {
|
if (cmd == MedtronicCommandType.GetHistoryData) {
|
||||||
binding.pumpStatusIcon.text = medtronicUtil.frameNumber?.let {
|
binding.pumpStatusIcon.text = medtronicUtil.frameNumber?.let {
|
||||||
resourceHelper.gs(cmdResourceId, medtronicUtil.pageNumber, medtronicUtil.frameNumber)
|
resourceHelper.gs(cmdResourceId!!, medtronicUtil.pageNumber, medtronicUtil.frameNumber)
|
||||||
}
|
}
|
||||||
?: resourceHelper.gs(R.string.medtronic_cmd_desc_get_history_request, medtronicUtil.pageNumber)
|
?: resourceHelper.gs(R.string.medtronic_cmd_desc_get_history_request, medtronicUtil.pageNumber)
|
||||||
} else {
|
} else {
|
||||||
binding.pumpStatusIcon.text = " " + (cmdResourceId?.let { resourceHelper.gs(it) }
|
binding.pumpStatusIcon.text = " " + (cmdResourceId?.let { resourceHelper.gs(it) }
|
||||||
?: cmd.getCommandDescription())
|
?: cmd.commandDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> aapsLogger.warn(LTag.PUMP, "Unknown pump state: " + medtronicPumpStatus.pumpDeviceState)
|
else ->
|
||||||
|
aapsLogger.warn(LTag.PUMP, "Unknown pump state: " + medtronicPumpStatus.pumpDeviceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
val status = commandQueue.spannedStatus()
|
val status = commandQueue.spannedStatus()
|
||||||
|
@ -298,26 +300,31 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
val bolus = medtronicPumpStatus.lastBolusAmount
|
val bolus = medtronicPumpStatus.lastBolusAmount
|
||||||
val bolusTime = medtronicPumpStatus.lastBolusTime
|
val bolusTime = medtronicPumpStatus.lastBolusTime
|
||||||
if (bolus != null && bolusTime != null) {
|
if (bolus != null && bolusTime != null) {
|
||||||
val agoMsc = System.currentTimeMillis() - medtronicPumpStatus.lastBolusTime.time
|
val agoMsc = System.currentTimeMillis() - bolusTime.time
|
||||||
val bolusMinAgo = agoMsc.toDouble() / 60.0 / 1000.0
|
val bolusMinAgo = agoMsc.toDouble() / 60.0 / 1000.0
|
||||||
val unit = resourceHelper.gs(R.string.insulin_unit_shortname)
|
val unit = resourceHelper.gs(R.string.insulin_unit_shortname)
|
||||||
val ago = when {
|
val ago = when {
|
||||||
agoMsc < 60 * 1000 -> resourceHelper.gs(R.string.medtronic_pump_connected_now)
|
agoMsc < 60 * 1000 -> resourceHelper.gs(R.string.medtronic_pump_connected_now)
|
||||||
bolusMinAgo < 60 -> dateUtil.minAgo(resourceHelper, medtronicPumpStatus.lastBolusTime.time)
|
bolusMinAgo < 60 -> dateUtil.minAgo(resourceHelper, bolusTime.time)
|
||||||
else -> dateUtil.hourAgo(medtronicPumpStatus.lastBolusTime.time, resourceHelper)
|
else -> dateUtil.hourAgo(bolusTime.time, resourceHelper)
|
||||||
}
|
}
|
||||||
binding.lastBolus.text = resourceHelper.gs(R.string.mdt_last_bolus, bolus, unit, ago)
|
binding.lastBolus.text = resourceHelper.gs(R.string.mdt_last_bolus, bolus, unit, ago)
|
||||||
} else {
|
} else {
|
||||||
binding.lastBolus.text = ""
|
binding.lastBolus.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val pumpState = pumpSync.expectedPumpState()
|
|
||||||
// base basal rate
|
// base basal rate
|
||||||
binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") "
|
binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") "
|
||||||
+ resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate))
|
+ resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate))
|
||||||
|
|
||||||
binding.tempBasal.text = pumpState.temporaryBasal?.toStringFull(dateUtil)
|
// TBR
|
||||||
?: ""
|
var tbrStr = ""
|
||||||
|
val tbrRemainingTime: Int? = medtronicPumpStatus.tbrRemainingTime
|
||||||
|
|
||||||
|
if (tbrRemainingTime != null) {
|
||||||
|
tbrStr = resourceHelper.gs(R.string.mdt_tbr_remaining, medtronicPumpStatus.tempBasalAmount, tbrRemainingTime);
|
||||||
|
}
|
||||||
|
binding.tempBasal.text = tbrStr
|
||||||
|
|
||||||
// battery
|
// battery
|
||||||
if (medtronicPumpStatus.batteryType == BatteryType.None || medtronicPumpStatus.batteryVoltage == null) {
|
if (medtronicPumpStatus.batteryType == BatteryType.None || medtronicPumpStatus.batteryVoltage == null) {
|
||||||
|
@ -331,7 +338,7 @@ class MedtronicFragment : DaggerFragment() {
|
||||||
binding.reservoir.text = resourceHelper.gs(R.string.reservoirvalue, medtronicPumpStatus.reservoirRemainingUnits, medtronicPumpStatus.reservoirFullUnits)
|
binding.reservoir.text = resourceHelper.gs(R.string.reservoirvalue, medtronicPumpStatus.reservoirRemainingUnits, medtronicPumpStatus.reservoirFullUnits)
|
||||||
warnColors.setColorInverse(binding.reservoir, medtronicPumpStatus.reservoirRemainingUnits, 50.0, 20.0)
|
warnColors.setColorInverse(binding.reservoir, medtronicPumpStatus.reservoirRemainingUnits, 50.0, 20.0)
|
||||||
|
|
||||||
medtronicPumpPlugin.rileyLinkService?.verifyConfiguration()
|
medtronicPumpPlugin.rileyLinkService.verifyConfiguration()
|
||||||
binding.errors.text = medtronicPumpStatus.errorInfo
|
binding.errors.text = medtronicPumpStatus.errorInfo
|
||||||
|
|
||||||
val showRileyLinkBatteryLevel: Boolean = rileyLinkServiceData.showBatteryLevel
|
val showRileyLinkBatteryLevel: Boolean = rileyLinkServiceData.showBatteryLevel
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,919 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm;
|
|
||||||
|
|
||||||
import android.os.SystemClock;
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RawHistoryPage;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkLongMessageBody;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkShortMessageBody;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.GetHistoryPageCarelinkMessageBody;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.MessageBody;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PacketType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpAckMessageBody;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpMessage;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Original file created by geoff on 5/30/16.
|
|
||||||
* <p>
|
|
||||||
* Split into 2 implementations, so that we can split it by target device. - Andy
|
|
||||||
* This was mostly rewritten from Original version, and lots of commands and
|
|
||||||
* functionality added.
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class MedtronicCommunicationManager extends RileyLinkCommunicationManager<PumpMessage> {
|
|
||||||
|
|
||||||
@Inject MedtronicPumpStatus medtronicPumpStatus;
|
|
||||||
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
|
|
||||||
@Inject MedtronicConverter medtronicConverter;
|
|
||||||
@Inject MedtronicUtil medtronicUtil;
|
|
||||||
@Inject MedtronicPumpHistoryDecoder medtronicPumpHistoryDecoder;
|
|
||||||
|
|
||||||
private final int MAX_COMMAND_TRIES = 3;
|
|
||||||
private final int DEFAULT_TIMEOUT = 2000;
|
|
||||||
private final long RILEYLINK_TIMEOUT = 15 * 60 * 1000; // 15 min
|
|
||||||
|
|
||||||
private String errorMessage;
|
|
||||||
private final boolean debugSetCommands = false;
|
|
||||||
|
|
||||||
private boolean doWakeUpBeforeCommand = true;
|
|
||||||
|
|
||||||
// This empty constructor must be kept, otherwise dagger injection might break!
|
|
||||||
@Inject
|
|
||||||
public MedtronicCommunicationManager() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public void onInit() {
|
|
||||||
// we can't do this in the constructor, as sp only gets injected after the constructor has returned
|
|
||||||
medtronicPumpStatus.previousConnection = sp.getLong(
|
|
||||||
RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PumpMessage createResponseMessage(byte[] payload) {
|
|
||||||
return new PumpMessage(aapsLogger, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPumpDeviceState(PumpDeviceState pumpDeviceState) {
|
|
||||||
this.medtronicPumpStatus.setPumpDeviceState(pumpDeviceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDoWakeUpBeforeCommand(boolean doWakeUp) {
|
|
||||||
this.doWakeUpBeforeCommand = doWakeUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDeviceReachable() {
|
|
||||||
return isDeviceReachable(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We do actual wakeUp and compare PumpModel with currently selected one. If returned model is
|
|
||||||
* not Unknown, pump is reachable.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isDeviceReachable(boolean canPreventTuneUp) {
|
|
||||||
|
|
||||||
PumpDeviceState state = medtronicPumpStatus.getPumpDeviceState();
|
|
||||||
|
|
||||||
if (state != PumpDeviceState.PumpUnreachable)
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.WakingUp);
|
|
||||||
|
|
||||||
for (int retry = 0; retry < 5; retry++) {
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "isDeviceReachable. Waking pump... " + (retry != 0 ? " (retry " + retry + ")" : ""));
|
|
||||||
|
|
||||||
boolean connected = connectToDevice();
|
|
||||||
|
|
||||||
if (connected)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
SystemClock.sleep(1000);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state != PumpDeviceState.PumpUnreachable)
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.PumpUnreachable);
|
|
||||||
|
|
||||||
if (!canPreventTuneUp) {
|
|
||||||
|
|
||||||
long diff = System.currentTimeMillis() - medtronicPumpStatus.lastConnection;
|
|
||||||
|
|
||||||
if (diff > RILEYLINK_TIMEOUT) {
|
|
||||||
serviceTaskExecutor.startTask(new WakeAndTuneTask(injector));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean connectToDevice() {
|
|
||||||
|
|
||||||
PumpDeviceState state = medtronicPumpStatus.getPumpDeviceState();
|
|
||||||
|
|
||||||
// check connection
|
|
||||||
|
|
||||||
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple
|
|
||||||
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(injector, pumpMsgContent), (byte) 0, (byte) 200,
|
|
||||||
(byte) 0, (byte) 0, 25000, (byte) 0);
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "wakeup: raw response is " + ByteUtil.shortHexString(rfSpyResponse.getRaw()));
|
|
||||||
|
|
||||||
if (rfSpyResponse.wasTimeout()) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "isDeviceReachable. Failed to find pump (timeout).");
|
|
||||||
} else if (rfSpyResponse.looksLikeRadioPacket()) {
|
|
||||||
RadioResponse radioResponse = new RadioResponse(injector);
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
radioResponse.init(rfSpyResponse.getRaw());
|
|
||||||
|
|
||||||
if (radioResponse.isValid()) {
|
|
||||||
|
|
||||||
PumpMessage pumpResponse = createResponseMessage(radioResponse.getPayload());
|
|
||||||
|
|
||||||
if (!pumpResponse.isValid()) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Response is invalid ! [interrupted=%b, timeout=%b]", rfSpyResponse.wasInterrupted(),
|
|
||||||
rfSpyResponse.wasTimeout()));
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// radioResponse.rssi;
|
|
||||||
Object dataResponse = medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), MedtronicCommandType.PumpModel,
|
|
||||||
pumpResponse.getRawContent());
|
|
||||||
|
|
||||||
MedtronicDeviceType pumpModel = (MedtronicDeviceType) dataResponse;
|
|
||||||
boolean valid = (pumpModel != MedtronicDeviceType.Unknown_Device);
|
|
||||||
|
|
||||||
if (medtronicUtil.getMedtronicPumpModel() == null && valid) {
|
|
||||||
medtronicUtil.setMedtronicPumpModel(pumpModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "isDeviceReachable. PumpModel is %s - Valid: %b (rssi=%d)", pumpModel.name(), valid,
|
|
||||||
radioResponse.rssi));
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
if (state == PumpDeviceState.PumpUnreachable)
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.WakingUp);
|
|
||||||
else
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
rememberLastGoodDeviceCommunicationTime();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (state != PumpDeviceState.PumpUnreachable)
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.PumpUnreachable);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to parse radio response: "
|
|
||||||
+ ByteUtil.shortHexString(rfSpyResponse.getRaw()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to decode radio response: "
|
|
||||||
+ ByteUtil.shortHexString(rfSpyResponse.getRaw()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Unknown response: " + ByteUtil.shortHexString(rfSpyResponse.getRaw()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean tryToConnectToDevice() {
|
|
||||||
return isDeviceReachable(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage runCommandWithArgs(PumpMessage msg) throws RileyLinkCommunicationException {
|
|
||||||
|
|
||||||
if (debugSetCommands)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: ");
|
|
||||||
|
|
||||||
PumpMessage rval;
|
|
||||||
PumpMessage shortMessage = makePumpMessage(msg.commandType, new CarelinkShortMessageBody(new byte[]{0}));
|
|
||||||
// look for ack from short message
|
|
||||||
PumpMessage shortResponse = sendAndListen(shortMessage);
|
|
||||||
if (shortResponse.commandType == MedtronicCommandType.CommandACK) {
|
|
||||||
if (debugSetCommands)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: Got ACK response");
|
|
||||||
|
|
||||||
rval = sendAndListen(msg);
|
|
||||||
if (debugSetCommands)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "2nd Response: " + rval);
|
|
||||||
|
|
||||||
return rval;
|
|
||||||
} else {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithArgs: Pump did not ack Attention packet");
|
|
||||||
return new PumpMessage(aapsLogger, "No ACK after Attention packet.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage runCommandWithFrames(MedtronicCommandType commandType, List<List<Byte>> frames)
|
|
||||||
throws RileyLinkCommunicationException {
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: " + commandType.name());
|
|
||||||
|
|
||||||
PumpMessage rval = null;
|
|
||||||
PumpMessage shortMessage = makePumpMessage(commandType, new CarelinkShortMessageBody(new byte[]{0}));
|
|
||||||
// look for ack from short message
|
|
||||||
PumpMessage shortResponse = sendAndListen(shortMessage);
|
|
||||||
|
|
||||||
if (shortResponse.commandType != MedtronicCommandType.CommandACK) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ack Attention packet");
|
|
||||||
|
|
||||||
return new PumpMessage(aapsLogger, "No ACK after start message.");
|
|
||||||
} else {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for Attention packet");
|
|
||||||
}
|
|
||||||
|
|
||||||
int frameNr = 1;
|
|
||||||
|
|
||||||
for (List<Byte> frame : frames) {
|
|
||||||
|
|
||||||
byte[] frameData = MedtronicUtil.createByteArray(frame);
|
|
||||||
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"Frame {} data:\n{}", frameNr, ByteUtil.getCompactString(frameData));
|
|
||||||
|
|
||||||
PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(frameData));
|
|
||||||
|
|
||||||
rval = sendAndListen(msg);
|
|
||||||
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"PumpResponse: " + rval);
|
|
||||||
|
|
||||||
if (rval.commandType != MedtronicCommandType.CommandACK) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ACK frame #" + frameNr);
|
|
||||||
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Run command with Frames FAILED (command=%s, response=%s)", commandType.name(),
|
|
||||||
rval.toString()));
|
|
||||||
|
|
||||||
return new PumpMessage(aapsLogger, "No ACK after frame #" + frameNr);
|
|
||||||
} else {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for frame #" + frameNr);
|
|
||||||
}
|
|
||||||
|
|
||||||
frameNr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rval;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpHistoryResult getPumpHistory(PumpHistoryEntry lastEntry, LocalDateTime targetDate) {
|
|
||||||
|
|
||||||
PumpHistoryResult pumpTotalResult = new PumpHistoryResult(aapsLogger, lastEntry, targetDate == null ? null
|
|
||||||
: DateTimeUtil.toATechDate(targetDate));
|
|
||||||
|
|
||||||
if (doWakeUpBeforeCommand)
|
|
||||||
wakeUp(receiverDeviceAwakeForMinutes, false);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Current command: " + medtronicUtil.getCurrentCommand());
|
|
||||||
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
|
|
||||||
boolean doneWithError = false;
|
|
||||||
|
|
||||||
for (int pageNumber = 0; pageNumber < 5; pageNumber++) {
|
|
||||||
|
|
||||||
RawHistoryPage rawHistoryPage = new RawHistoryPage(aapsLogger);
|
|
||||||
// wakeUp(receiverDeviceAwakeForMinutes, false);
|
|
||||||
PumpMessage getHistoryMsg = makePumpMessage(MedtronicCommandType.GetHistoryData,
|
|
||||||
new GetHistoryPageCarelinkMessageBody(pageNumber));
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "getPumpHistory: Page " + pageNumber);
|
|
||||||
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData()));
|
|
||||||
// Ask the pump to transfer history (we get first frame?)
|
|
||||||
|
|
||||||
PumpMessage firstResponse = null;
|
|
||||||
boolean failed = false;
|
|
||||||
|
|
||||||
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, null);
|
|
||||||
|
|
||||||
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
firstResponse = runCommandWithArgs(getHistoryMsg);
|
|
||||||
failed = false;
|
|
||||||
break;
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "First call for PumpHistory failed (retry=%d)", retries));
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed) {
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
return pumpTotalResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents()));
|
|
||||||
|
|
||||||
PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody());
|
|
||||||
GetHistoryPageCarelinkMessageBody currentResponse = new GetHistoryPageCarelinkMessageBody(firstResponse
|
|
||||||
.getMessageBody().getTxData());
|
|
||||||
int expectedFrameNum = 1;
|
|
||||||
boolean done = false;
|
|
||||||
// while (expectedFrameNum == currentResponse.getFrameNumber()) {
|
|
||||||
|
|
||||||
int failures = 0;
|
|
||||||
while (!done) {
|
|
||||||
// examine current response for problems.
|
|
||||||
byte[] frameData = currentResponse.getFrameData();
|
|
||||||
if ((frameData != null) && (frameData.length > 0)
|
|
||||||
&& currentResponse.getFrameNumber() == expectedFrameNum) {
|
|
||||||
// success! got a frame.
|
|
||||||
if (frameData.length != 64) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Expected frame of length 64, got frame of length " + frameData.length);
|
|
||||||
// but append it anyway?
|
|
||||||
}
|
|
||||||
// handle successful frame data
|
|
||||||
rawHistoryPage.appendData(currentResponse.getFrameData());
|
|
||||||
// RileyLinkMedtronicService.getInstance().announceProgress(((100 / 16) *
|
|
||||||
// currentResponse.getFrameNumber() + 1));
|
|
||||||
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber,
|
|
||||||
currentResponse.getFrameNumber());
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Got frame %d of Page %d", currentResponse.getFrameNumber(), pageNumber));
|
|
||||||
// Do we need to ask for the next frame?
|
|
||||||
if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722
|
|
||||||
expectedFrameNum++;
|
|
||||||
} else {
|
|
||||||
done = true; // successful completion
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (frameData == null) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "null frame data, retrying");
|
|
||||||
} else if (currentResponse.getFrameNumber() != expectedFrameNum) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Expected frame number %d, received %d (retrying)", expectedFrameNum,
|
|
||||||
currentResponse.getFrameNumber()));
|
|
||||||
} else if (frameData.length == 0) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Frame has zero length, retrying");
|
|
||||||
}
|
|
||||||
failures++;
|
|
||||||
if (failures == 6) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM,
|
|
||||||
String.format(Locale.ENGLISH, "getPumpHistory: 6 failures in attempting to download frame %d of page %d, giving up.",
|
|
||||||
expectedFrameNum, pageNumber));
|
|
||||||
done = true; // failure completion.
|
|
||||||
doneWithError = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
// ask for next frame
|
|
||||||
PumpMessage nextMsg = null;
|
|
||||||
|
|
||||||
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
nextMsg = sendAndListen(ackMsg);
|
|
||||||
break;
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Problem acknowledging frame response. (retry=%d)", retries));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextMsg != null)
|
|
||||||
currentResponse = new GetHistoryPageCarelinkMessageBody(nextMsg.getMessageBody().getTxData());
|
|
||||||
else {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "We couldn't acknowledge frame from pump, aborting operation.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawHistoryPage.getLength() != 1024) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "getPumpHistory: short page. Expected length of 1024, found length of "
|
|
||||||
+ rawHistoryPage.getLength());
|
|
||||||
doneWithError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rawHistoryPage.isChecksumOK()) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "getPumpHistory: checksum is wrong");
|
|
||||||
doneWithError = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doneWithError) {
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
return pumpTotalResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawHistoryPage.dumpToDebug();
|
|
||||||
|
|
||||||
List<PumpHistoryEntry> medtronicHistoryEntries = medtronicPumpHistoryDecoder.processPageAndCreateRecords(rawHistoryPage);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Found %d history entries.", medtronicHistoryEntries.size()));
|
|
||||||
|
|
||||||
pumpTotalResult.addHistoryEntries(medtronicHistoryEntries, pageNumber);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Search status: Search finished: %b", pumpTotalResult.isSearchFinished()));
|
|
||||||
|
|
||||||
if (pumpTotalResult.isSearchFinished()) {
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
return pumpTotalResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
return pumpTotalResult;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getErrorResponse() {
|
|
||||||
return this.errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] createPumpMessageContent(RLMessageType type) {
|
|
||||||
switch (type) {
|
|
||||||
case PowerOn:
|
|
||||||
return medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.RFPowerOn, //
|
|
||||||
new byte[]{2, 1, (byte) receiverDeviceAwakeForMinutes}); // maybe this is better FIXME
|
|
||||||
|
|
||||||
case ReadSimpleData:
|
|
||||||
return medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.PumpModel, null);
|
|
||||||
}
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage makePumpMessage(MedtronicCommandType messageType, byte[] body) {
|
|
||||||
return makePumpMessage(messageType, body == null ? new CarelinkShortMessageBody()
|
|
||||||
: new CarelinkShortMessageBody(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage makePumpMessage(MedtronicCommandType messageType) {
|
|
||||||
return makePumpMessage(messageType, (byte[]) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage makePumpMessage(MedtronicCommandType messageType, MessageBody messageBody) {
|
|
||||||
PumpMessage msg = new PumpMessage(aapsLogger);
|
|
||||||
msg.init(PacketType.Carelink, rileyLinkServiceData.pumpIDBytes, messageType, messageBody);
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage sendAndGetResponse(MedtronicCommandType commandType) throws RileyLinkCommunicationException {
|
|
||||||
|
|
||||||
return sendAndGetResponse(commandType, null, DEFAULT_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main wrapper method for sending data - (for getting responses)
|
|
||||||
*
|
|
||||||
* @param commandType
|
|
||||||
* @param bodyData
|
|
||||||
* @param timeoutMs
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private PumpMessage sendAndGetResponse(MedtronicCommandType commandType, byte[] bodyData, int timeoutMs)
|
|
||||||
throws RileyLinkCommunicationException {
|
|
||||||
// wakeUp
|
|
||||||
if (doWakeUpBeforeCommand)
|
|
||||||
wakeUp(receiverDeviceAwakeForMinutes, false);
|
|
||||||
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
|
|
||||||
|
|
||||||
// create message
|
|
||||||
PumpMessage msg;
|
|
||||||
|
|
||||||
if (bodyData == null)
|
|
||||||
msg = makePumpMessage(commandType);
|
|
||||||
else
|
|
||||||
msg = makePumpMessage(commandType, bodyData);
|
|
||||||
|
|
||||||
// send and wait for response
|
|
||||||
PumpMessage response = sendAndListen(msg, timeoutMs);
|
|
||||||
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private PumpMessage sendAndListen(PumpMessage msg) throws RileyLinkCommunicationException {
|
|
||||||
return sendAndListen(msg, 4000); // 2000
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// All pump communications go through this function.
|
|
||||||
@Override
|
|
||||||
protected PumpMessage sendAndListen(PumpMessage msg, int timeout_ms) throws RileyLinkCommunicationException {
|
|
||||||
return super.sendAndListen(msg, timeout_ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType) {
|
|
||||||
|
|
||||||
return sendAndGetResponseWithCheck(commandType, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType, byte[] bodyData) {
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: " + commandType);
|
|
||||||
|
|
||||||
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
PumpMessage response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
|
|
||||||
|
|
||||||
String check = checkResponseContent(response, commandType.commandDescription, commandType.expectedLength);
|
|
||||||
|
|
||||||
if (check == null) {
|
|
||||||
|
|
||||||
Object dataResponse = medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), commandType, response.getRawContent());
|
|
||||||
|
|
||||||
if (dataResponse != null) {
|
|
||||||
this.errorMessage = null;
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name(), dataResponse));
|
|
||||||
|
|
||||||
return dataResponse;
|
|
||||||
} else {
|
|
||||||
this.errorMessage = "Error decoding response.";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.errorMessage = check;
|
|
||||||
// return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String checkResponseContent(PumpMessage response, String method, int expectedLength) {
|
|
||||||
|
|
||||||
if (!response.isValid()) {
|
|
||||||
String responseData = String.format("%s: Invalid response.", method);
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, responseData);
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] contents = response.getRawContent();
|
|
||||||
|
|
||||||
if (contents != null) {
|
|
||||||
if (contents.length >= expectedLength) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Content: %s", method, ByteUtil.shortHexString(contents)));
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
String responseData = String.format(
|
|
||||||
"%s: Cannot return data. Data is too short [expected=%s, received=%s].", method, ""
|
|
||||||
+ expectedLength, "" + contents.length);
|
|
||||||
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, responseData);
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String responseData = String.format("%s: Cannot return data. Null response.", method);
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, responseData);
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// PUMP SPECIFIC COMMANDS
|
|
||||||
|
|
||||||
public Float getRemainingInsulin() {
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRemainingInsulin);
|
|
||||||
|
|
||||||
return responseObject == null ? null : (Float) responseObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MedtronicDeviceType getPumpModel() {
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.PumpModel);
|
|
||||||
|
|
||||||
return responseObject == null ? null : (MedtronicDeviceType) responseObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public BasalProfile getBasalProfile() {
|
|
||||||
|
|
||||||
// wakeUp
|
|
||||||
if (doWakeUpBeforeCommand)
|
|
||||||
wakeUp(receiverDeviceAwakeForMinutes, false);
|
|
||||||
|
|
||||||
MedtronicCommandType commandType = MedtronicCommandType.GetBasalProfileSTD;
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: " + commandType);
|
|
||||||
|
|
||||||
medtronicUtil.setCurrentCommand(commandType);
|
|
||||||
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
|
|
||||||
|
|
||||||
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
// create message
|
|
||||||
PumpMessage msg;
|
|
||||||
|
|
||||||
msg = makePumpMessage(commandType);
|
|
||||||
|
|
||||||
// send and wait for response
|
|
||||||
|
|
||||||
PumpMessage response = sendAndListen(msg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
|
|
||||||
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getRawContent()));
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getMessageBody().getTxData()));
|
|
||||||
|
|
||||||
String check = checkResponseContent(response, commandType.commandDescription, 1);
|
|
||||||
|
|
||||||
byte[] data = null;
|
|
||||||
|
|
||||||
if (check == null) {
|
|
||||||
|
|
||||||
data = response.getRawContentOfFrame();
|
|
||||||
|
|
||||||
PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody());
|
|
||||||
|
|
||||||
while (checkIfWeHaveMoreData(commandType, response, data)) {
|
|
||||||
|
|
||||||
response = sendAndListen(ackMsg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
|
|
||||||
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs, HexDump.toHexStringDisplayable(response2.getRawContent()));
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs,
|
|
||||||
// HexDump.toHexStringDisplayable(response2.getMessageBody().getTxData()));
|
|
||||||
|
|
||||||
String check2 = checkResponseContent(response, commandType.commandDescription, 1);
|
|
||||||
|
|
||||||
if (check2 == null) {
|
|
||||||
|
|
||||||
data = ByteUtil.concat(data, response.getRawContentOfFrame());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.errorMessage = check2;
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "Error with response got GetProfile: " + check2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
errorMessage = check;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasalProfile basalProfile = (BasalProfile) medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), commandType, data);
|
|
||||||
|
|
||||||
if (basalProfile != null) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name(), basalProfile));
|
|
||||||
|
|
||||||
medtronicUtil.setCurrentCommand(null);
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
return basalProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Error reading profile in max retries.");
|
|
||||||
medtronicUtil.setCurrentCommand(null);
|
|
||||||
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean checkIfWeHaveMoreData(MedtronicCommandType commandType, PumpMessage response, byte[] data) {
|
|
||||||
|
|
||||||
if (commandType == MedtronicCommandType.GetBasalProfileSTD || //
|
|
||||||
commandType == MedtronicCommandType.GetBasalProfileA || //
|
|
||||||
commandType == MedtronicCommandType.GetBasalProfileB) {
|
|
||||||
byte[] responseRaw = response.getRawContentOfFrame();
|
|
||||||
|
|
||||||
int last = responseRaw.length - 1;
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Length: " + data.length);
|
|
||||||
|
|
||||||
if (data.length >= BasalProfile.MAX_RAW_DATA_SIZE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseRaw.length < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(responseRaw[last] == 0x00 && responseRaw[last - 1] == 0x00 && responseRaw[last - 2] == 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ClockDTO getPumpTime() {
|
|
||||||
|
|
||||||
ClockDTO clockDTO = new ClockDTO();
|
|
||||||
clockDTO.localDeviceTime = new LocalDateTime();
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRealTimeClock);
|
|
||||||
|
|
||||||
if (responseObject != null) {
|
|
||||||
clockDTO.pumpTime = (LocalDateTime) responseObject;
|
|
||||||
return clockDTO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TempBasalPair getTemporaryBasal() {
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.ReadTemporaryBasal);
|
|
||||||
|
|
||||||
return responseObject == null ? null : (TempBasalPair) responseObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, PumpSettingDTO> getPumpSettings() {
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.getSettings(medtronicUtil
|
|
||||||
.getMedtronicPumpModel()));
|
|
||||||
|
|
||||||
return responseObject == null ? null : (Map<String, PumpSettingDTO>) responseObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Boolean setBolus(double units) {
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "setBolus: " + units);
|
|
||||||
|
|
||||||
return setCommand(MedtronicCommandType.SetBolus, medtronicUtil.getBolusStrokes(units));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean setTBR(TempBasalPair tbr) {
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "setTBR: " + tbr.getDescription());
|
|
||||||
|
|
||||||
return setCommand(MedtronicCommandType.SetTemporaryBasal, tbr.getAsRawData());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Boolean setPumpTime() {
|
|
||||||
|
|
||||||
GregorianCalendar gc = new GregorianCalendar();
|
|
||||||
gc.add(Calendar.SECOND, 5);
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "setPumpTime: " + DateTimeUtil.toString(gc));
|
|
||||||
|
|
||||||
int i = 1;
|
|
||||||
byte[] data = new byte[8];
|
|
||||||
data[0] = 7;
|
|
||||||
data[i] = (byte) gc.get(Calendar.HOUR_OF_DAY);
|
|
||||||
data[i + 1] = (byte) gc.get(Calendar.MINUTE);
|
|
||||||
data[i + 2] = (byte) gc.get(Calendar.SECOND);
|
|
||||||
|
|
||||||
byte[] yearByte = MedtronicUtil.getByteArrayFromUnsignedShort(gc.get(Calendar.YEAR), true);
|
|
||||||
|
|
||||||
data[i + 3] = yearByte[0];
|
|
||||||
data[i + 4] = yearByte[1];
|
|
||||||
|
|
||||||
data[i + 5] = (byte) (gc.get(Calendar.MONTH) + 1);
|
|
||||||
data[i + 6] = (byte) gc.get(Calendar.DAY_OF_MONTH);
|
|
||||||
|
|
||||||
//aapsLogger.info(LTag.PUMPCOMM,"setPumpTime: Body: " + ByteUtil.getHex(data));
|
|
||||||
|
|
||||||
return setCommand(MedtronicCommandType.SetRealTimeClock, data);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean setCommand(MedtronicCommandType commandType, byte[] body) {
|
|
||||||
|
|
||||||
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.doWakeUpBeforeCommand)
|
|
||||||
wakeUp(false);
|
|
||||||
|
|
||||||
if (debugSetCommands)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Body - %s", commandType.getCommandDescription(),
|
|
||||||
ByteUtil.getHex(body)));
|
|
||||||
|
|
||||||
PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(body));
|
|
||||||
|
|
||||||
PumpMessage pumpMessage = runCommandWithArgs(msg);
|
|
||||||
|
|
||||||
if (debugSetCommands)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: %s", commandType.getCommandDescription(), pumpMessage.getResponseContent()));
|
|
||||||
|
|
||||||
if (pumpMessage.commandType == MedtronicCommandType.CommandACK) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "We received non-ACK response from pump: " + pumpMessage.getResponseContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean cancelTBR() {
|
|
||||||
return setTBR(new TempBasalPair(0.0d, false, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public BatteryStatusDTO getRemainingBattery() {
|
|
||||||
|
|
||||||
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetBatteryStatus);
|
|
||||||
|
|
||||||
return responseObject == null ? null : (BatteryStatusDTO) responseObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Boolean setBasalProfile(BasalProfile basalProfile) {
|
|
||||||
|
|
||||||
List<List<Byte>> basalProfileFrames = medtronicUtil.getBasalProfileFrames(basalProfile.getRawData());
|
|
||||||
|
|
||||||
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
|
|
||||||
|
|
||||||
PumpMessage responseMessage = null;
|
|
||||||
try {
|
|
||||||
responseMessage = runCommandWithFrames(MedtronicCommandType.SetBasalProfileSTD,
|
|
||||||
basalProfileFrames);
|
|
||||||
|
|
||||||
if (responseMessage.commandType == MedtronicCommandType.CommandACK)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (RileyLinkCommunicationException e) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseMessage != null)
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Set Basal Profile: Invalid response: commandType=%s,rawData=%s", responseMessage.commandType, ByteUtil.shortHexString(responseMessage.getRawContent())));
|
|
||||||
else
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Set Basal Profile: Null response.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,679 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm
|
||||||
|
|
||||||
|
import android.os.SystemClock
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RawHistoryPage
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.*
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType.Companion.getSettings
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.Companion.createByteArray
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.Companion.getByteArrayFromUnsignedShort
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original file created by geoff on 5/30/16.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Split into 2 implementations, so that we can split it by target device. - Andy
|
||||||
|
* This was mostly rewritten from Original version, and lots of commands and
|
||||||
|
* functionality added.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class MedtronicCommunicationManager // This empty constructor must be kept, otherwise dagger injection might break!
|
||||||
|
@Inject constructor() : RileyLinkCommunicationManager<PumpMessage?>() {
|
||||||
|
|
||||||
|
@Inject lateinit var medtronicPumpStatus: MedtronicPumpStatus
|
||||||
|
@Inject lateinit var medtronicPumpPlugin: MedtronicPumpPlugin
|
||||||
|
@Inject lateinit var medtronicConverter: MedtronicConverter
|
||||||
|
@Inject lateinit var medtronicUtil: MedtronicUtil
|
||||||
|
@Inject lateinit var medtronicPumpHistoryDecoder: MedtronicPumpHistoryDecoder
|
||||||
|
|
||||||
|
private val MAX_COMMAND_TRIES = 3
|
||||||
|
private val DEFAULT_TIMEOUT = 2000
|
||||||
|
private val RILEYLINK_TIMEOUT: Long = 15 * 60 * 1000 // 15 min
|
||||||
|
|
||||||
|
var errorResponse: String? = null
|
||||||
|
private set
|
||||||
|
private val debugSetCommands = false
|
||||||
|
private var doWakeUpBeforeCommand = true
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
fun onInit(): Unit {
|
||||||
|
// we can't do this in the constructor, as sp only gets injected after the constructor has returned
|
||||||
|
medtronicPumpStatus.previousConnection = sp.getLong(
|
||||||
|
RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createResponseMessage(payload: ByteArray): PumpMessage {
|
||||||
|
return PumpMessage(aapsLogger, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPumpDeviceState(pumpDeviceState: PumpDeviceState) {
|
||||||
|
medtronicPumpStatus.pumpDeviceState = pumpDeviceState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDoWakeUpBeforeCommand(doWakeUp: Boolean) {
|
||||||
|
doWakeUpBeforeCommand = doWakeUp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDeviceReachable(): Boolean {
|
||||||
|
return isDeviceReachable(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We do actual wakeUp and compare PumpModel with currently selected one. If returned model is
|
||||||
|
* not Unknown, pump is reachable.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun isDeviceReachable(canPreventTuneUp: Boolean): Boolean {
|
||||||
|
val state = medtronicPumpStatus.pumpDeviceState
|
||||||
|
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.WakingUp
|
||||||
|
for (retry in 0..4) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "isDeviceReachable. Waking pump... " + if (retry != 0) " (retry $retry)" else "")
|
||||||
|
val connected = connectToDevice()
|
||||||
|
if (connected) return true
|
||||||
|
SystemClock.sleep(1000)
|
||||||
|
}
|
||||||
|
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.PumpUnreachable
|
||||||
|
if (!canPreventTuneUp) {
|
||||||
|
val diff = System.currentTimeMillis() - medtronicPumpStatus.lastConnection
|
||||||
|
if (diff > RILEYLINK_TIMEOUT) {
|
||||||
|
serviceTaskExecutor.startTask(WakeAndTuneTask(injector))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun connectToDevice(): Boolean {
|
||||||
|
val state = medtronicPumpStatus.pumpDeviceState
|
||||||
|
|
||||||
|
// check connection
|
||||||
|
val pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData) // simple
|
||||||
|
val rfSpyResponse = rfspy.transmitThenReceive(RadioPacket(injector, pumpMsgContent), 0.toByte(), 200.toByte(),
|
||||||
|
0.toByte(), 0.toByte(), 25000, 0.toByte())
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "wakeup: raw response is " + ByteUtil.shortHexString(rfSpyResponse.raw))
|
||||||
|
if (rfSpyResponse.wasTimeout()) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "isDeviceReachable. Failed to find pump (timeout).")
|
||||||
|
} else if (rfSpyResponse.looksLikeRadioPacket()) {
|
||||||
|
val radioResponse = RadioResponse(injector)
|
||||||
|
try {
|
||||||
|
radioResponse.init(rfSpyResponse.raw)
|
||||||
|
if (radioResponse.isValid) {
|
||||||
|
val pumpResponse = createResponseMessage(radioResponse.payload)
|
||||||
|
if (!pumpResponse.isValid) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Response is invalid ! [interrupted=%b, timeout=%b]", rfSpyResponse.wasInterrupted(),
|
||||||
|
rfSpyResponse.wasTimeout()))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// radioResponse.rssi;
|
||||||
|
val dataResponse = medtronicConverter.decodeModel(pumpResponse.rawContent)
|
||||||
|
val pumpModel = dataResponse as MedtronicDeviceType?
|
||||||
|
val valid = pumpModel !== MedtronicDeviceType.Unknown_Device
|
||||||
|
if (!medtronicUtil.isModelSet && valid) {
|
||||||
|
medtronicUtil.medtronicPumpModel = pumpModel!!
|
||||||
|
medtronicUtil.isModelSet = true
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "isDeviceReachable. PumpModel is %s - Valid: %b (rssi=%d)", medtronicUtil.medtronicPumpModel, valid,
|
||||||
|
radioResponse.rssi))
|
||||||
|
if (valid) {
|
||||||
|
if (state === PumpDeviceState.PumpUnreachable)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.WakingUp
|
||||||
|
else
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
rememberLastGoodDeviceCommunicationTime()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.PumpUnreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to parse radio response: "
|
||||||
|
+ ByteUtil.shortHexString(rfSpyResponse.raw))
|
||||||
|
}
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to decode radio response: "
|
||||||
|
+ ByteUtil.shortHexString(rfSpyResponse.raw))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Unknown response: " + ByteUtil.shortHexString(rfSpyResponse.raw))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tryToConnectToDevice(): Boolean {
|
||||||
|
return isDeviceReachable(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RileyLinkCommunicationException::class)
|
||||||
|
private fun runCommandWithArgs(msg: PumpMessage): PumpMessage {
|
||||||
|
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: ")
|
||||||
|
val rval: PumpMessage
|
||||||
|
val shortMessage = makePumpMessage(msg.commandType, CarelinkShortMessageBody(byteArrayOf(0)))
|
||||||
|
// look for ack from short message
|
||||||
|
val shortResponse = sendAndListen(shortMessage)
|
||||||
|
return if (shortResponse.commandType === MedtronicCommandType.CommandACK) {
|
||||||
|
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: Got ACK response")
|
||||||
|
rval = sendAndListen(msg)
|
||||||
|
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "2nd Response: $rval")
|
||||||
|
rval
|
||||||
|
} else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithArgs: Pump did not ack Attention packet")
|
||||||
|
PumpMessage(aapsLogger, "No ACK after Attention packet.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RileyLinkCommunicationException::class)
|
||||||
|
private fun runCommandWithFrames(commandType: MedtronicCommandType, frames: List<List<Byte>>): PumpMessage? {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: " + commandType.name)
|
||||||
|
var rval: PumpMessage? = null
|
||||||
|
val shortMessage = makePumpMessage(commandType, CarelinkShortMessageBody(byteArrayOf(0)))
|
||||||
|
// look for ack from short message
|
||||||
|
val shortResponse = sendAndListen(shortMessage)
|
||||||
|
if (shortResponse.commandType !== MedtronicCommandType.CommandACK) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ack Attention packet")
|
||||||
|
return PumpMessage(aapsLogger, "No ACK after start message.")
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for Attention packet")
|
||||||
|
}
|
||||||
|
var frameNr = 1
|
||||||
|
for (frame in frames) {
|
||||||
|
val frameData = createByteArray(frame)
|
||||||
|
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"Frame {} data:\n{}", frameNr, ByteUtil.getCompactString(frameData));
|
||||||
|
val msg = makePumpMessage(commandType, CarelinkLongMessageBody(frameData))
|
||||||
|
rval = sendAndListen(msg)
|
||||||
|
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"PumpResponse: " + rval);
|
||||||
|
if (rval.commandType !== MedtronicCommandType.CommandACK) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ACK frame #$frameNr")
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Run command with Frames FAILED (command=%s, response=%s)", commandType.name,
|
||||||
|
rval.toString()))
|
||||||
|
return PumpMessage(aapsLogger, "No ACK after frame #$frameNr")
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for frame #$frameNr")
|
||||||
|
}
|
||||||
|
frameNr++
|
||||||
|
}
|
||||||
|
return rval
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPumpHistory(lastEntry: PumpHistoryEntry?, targetDate: LocalDateTime?): PumpHistoryResult {
|
||||||
|
val pumpTotalResult = PumpHistoryResult(aapsLogger, lastEntry, if (targetDate == null) null else DateTimeUtil.toATechDate(targetDate))
|
||||||
|
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Current command: " + medtronicUtil.getCurrentCommand())
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
|
||||||
|
var doneWithError = false
|
||||||
|
for (pageNumber in 0..4) {
|
||||||
|
val rawHistoryPage = RawHistoryPage(aapsLogger)
|
||||||
|
// wakeUp(receiverDeviceAwakeForMinutes, false);
|
||||||
|
val getHistoryMsg = makePumpMessage(MedtronicCommandType.GetHistoryData,
|
||||||
|
GetHistoryPageCarelinkMessageBody(pageNumber))
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "getPumpHistory: Page $pageNumber")
|
||||||
|
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData()));
|
||||||
|
// Ask the pump to transfer history (we get first frame?)
|
||||||
|
var firstResponse: PumpMessage? = null
|
||||||
|
var failed = false
|
||||||
|
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, null)
|
||||||
|
for (retries in 0 until MAX_COMMAND_TRIES) {
|
||||||
|
try {
|
||||||
|
firstResponse = runCommandWithArgs(getHistoryMsg)
|
||||||
|
failed = false
|
||||||
|
break
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "First call for PumpHistory failed (retry=%d)", retries))
|
||||||
|
failed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failed) {
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return pumpTotalResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents()));
|
||||||
|
val ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, PumpAckMessageBody())
|
||||||
|
var currentResponse = GetHistoryPageCarelinkMessageBody(firstResponse!!.messageBody!!.txData)
|
||||||
|
var expectedFrameNum = 1
|
||||||
|
var done = false
|
||||||
|
// while (expectedFrameNum == currentResponse.getFrameNumber()) {
|
||||||
|
var failures = 0
|
||||||
|
while (!done) {
|
||||||
|
// examine current response for problems.
|
||||||
|
val frameData = currentResponse.frameData
|
||||||
|
if (frameData.size > 0 && currentResponse.frameNumber == expectedFrameNum) {
|
||||||
|
// success! got a frame.
|
||||||
|
if (frameData.size != 64) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Expected frame of length 64, got frame of length " + frameData.size)
|
||||||
|
// but append it anyway?
|
||||||
|
}
|
||||||
|
// handle successful frame data
|
||||||
|
rawHistoryPage.appendData(currentResponse.frameData)
|
||||||
|
// RileyLinkMedtronicService.getInstance().announceProgress(((100 / 16) *
|
||||||
|
// currentResponse.getFrameNumber() + 1));
|
||||||
|
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber,
|
||||||
|
currentResponse.frameNumber)
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Got frame %d of Page %d", currentResponse.frameNumber, pageNumber))
|
||||||
|
// Do we need to ask for the next frame?
|
||||||
|
if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722
|
||||||
|
expectedFrameNum++
|
||||||
|
} else {
|
||||||
|
done = true // successful completion
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (frameData.size == 0) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "null frame data, retrying")
|
||||||
|
} else if (currentResponse.frameNumber != expectedFrameNum) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Expected frame number %d, received %d (retrying)", expectedFrameNum,
|
||||||
|
currentResponse.frameNumber))
|
||||||
|
} else if (frameData.size == 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Frame has zero length, retrying")
|
||||||
|
}
|
||||||
|
failures++
|
||||||
|
if (failures == 6) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: 6 failures in attempting to download frame %d of page %d, giving up.",
|
||||||
|
expectedFrameNum, pageNumber))
|
||||||
|
done = true // failure completion.
|
||||||
|
doneWithError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!done) {
|
||||||
|
// ask for next frame
|
||||||
|
var nextMsg: PumpMessage? = null
|
||||||
|
for (retries in 0 until MAX_COMMAND_TRIES) {
|
||||||
|
try {
|
||||||
|
nextMsg = sendAndListen(ackMsg)
|
||||||
|
break
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Problem acknowledging frame response. (retry=%d)", retries))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextMsg != null)
|
||||||
|
currentResponse = GetHistoryPageCarelinkMessageBody(nextMsg.messageBody!!.txData)
|
||||||
|
else {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "We couldn't acknowledge frame from pump, aborting operation.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rawHistoryPage.length != 1024) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "getPumpHistory: short page. Expected length of 1024, found length of "
|
||||||
|
+ rawHistoryPage.length)
|
||||||
|
doneWithError = true
|
||||||
|
}
|
||||||
|
if (!rawHistoryPage.isChecksumOK) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "getPumpHistory: checksum is wrong")
|
||||||
|
doneWithError = true
|
||||||
|
}
|
||||||
|
if (doneWithError) {
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return pumpTotalResult
|
||||||
|
}
|
||||||
|
rawHistoryPage.dumpToDebug()
|
||||||
|
val medtronicHistoryEntries = medtronicPumpHistoryDecoder.processPageAndCreateRecords(rawHistoryPage)
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Found %d history entries.", medtronicHistoryEntries.size))
|
||||||
|
pumpTotalResult.addHistoryEntries(medtronicHistoryEntries) //, pageNumber)
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Search status: Search finished: %b", pumpTotalResult.isSearchFinished))
|
||||||
|
if (pumpTotalResult.isSearchFinished) {
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return pumpTotalResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return pumpTotalResult
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createPumpMessageContent(type: RLMessageType): ByteArray {
|
||||||
|
return when (type) {
|
||||||
|
RLMessageType.PowerOn -> medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.RFPowerOn, byteArrayOf(2, 1, receiverDeviceAwakeForMinutes.toByte()))
|
||||||
|
RLMessageType.ReadSimpleData -> medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.PumpModel, null)
|
||||||
|
else -> ByteArray(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePumpMessage(messageType: MedtronicCommandType, body: ByteArray? = null): PumpMessage {
|
||||||
|
return makePumpMessage(messageType, body?.let { CarelinkShortMessageBody(it) }
|
||||||
|
?: CarelinkShortMessageBody())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePumpMessage(messageType: MedtronicCommandType?, messageBody: MessageBody): PumpMessage {
|
||||||
|
val msg = PumpMessage(aapsLogger)
|
||||||
|
msg.init(PacketType.Carelink, rileyLinkServiceData.pumpIDBytes, messageType, messageBody)
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main wrapper method for sending data - (for getting responses)
|
||||||
|
*
|
||||||
|
* @param commandType
|
||||||
|
* @param bodyData
|
||||||
|
* @param timeoutMs
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Throws(RileyLinkCommunicationException::class)
|
||||||
|
private fun sendAndGetResponse(commandType: MedtronicCommandType, bodyData: ByteArray? = null, timeoutMs: Int = DEFAULT_TIMEOUT): PumpMessage {
|
||||||
|
// wakeUp
|
||||||
|
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
|
||||||
|
|
||||||
|
// create message
|
||||||
|
val msg: PumpMessage
|
||||||
|
msg = bodyData?.let { makePumpMessage(commandType, it) } ?: makePumpMessage(commandType)
|
||||||
|
|
||||||
|
// send and wait for response
|
||||||
|
val response = sendAndListen(msg, timeoutMs)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(RileyLinkCommunicationException::class)
|
||||||
|
private fun sendAndListen(msg: PumpMessage): PumpMessage {
|
||||||
|
return sendAndListen(msg, 4000) // 2000
|
||||||
|
}
|
||||||
|
|
||||||
|
// All pump communications go through this function.
|
||||||
|
@Throws(RileyLinkCommunicationException::class)
|
||||||
|
protected /*override*/ fun sendAndListen(msg: PumpMessage, timeout_ms: Int): PumpMessage {
|
||||||
|
return super.sendAndListen(msg, timeout_ms)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> sendAndGetResponseWithCheck(
|
||||||
|
commandType: MedtronicCommandType,
|
||||||
|
bodyData: ByteArray? = null,
|
||||||
|
decode: (pumpType: PumpType, commandType: MedtronicCommandType, rawContent: ByteArray) -> T
|
||||||
|
): T? {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: $commandType")
|
||||||
|
for (retries in 0 until MAX_COMMAND_TRIES) {
|
||||||
|
try {
|
||||||
|
val response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
|
||||||
|
val check = checkResponseContent(response, commandType.commandDescription, commandType.expectedLength)
|
||||||
|
if (check == null) {
|
||||||
|
|
||||||
|
checkResponseRawContent(response.rawContent, commandType) { return@sendAndGetResponseWithCheck null }
|
||||||
|
|
||||||
|
val dataResponse = decode(medtronicPumpStatus.pumpType, commandType, response.rawContent)
|
||||||
|
if (dataResponse != null) {
|
||||||
|
errorResponse = null
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name, dataResponse))
|
||||||
|
return dataResponse
|
||||||
|
} else {
|
||||||
|
errorResponse = "Error decoding response."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorResponse = check
|
||||||
|
// return null;
|
||||||
|
}
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun checkResponseRawContent(rawContent: ByteArray?, commandType: MedtronicCommandType, errorCase: () -> Unit) {
|
||||||
|
if (rawContent?.isEmpty() != false && commandType != MedtronicCommandType.PumpModel) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Content is empty or too short, no data to convert (type=%s,isNull=%b,length=%s)",
|
||||||
|
commandType.name, rawContent == null, rawContent?.size ?: "-"))
|
||||||
|
errorCase.invoke()
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Raw response before convert: " + ByteUtil.shortHexString(rawContent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkResponseContent(response: PumpMessage, method: String, expectedLength: Int): String? {
|
||||||
|
if (!response.isValid) {
|
||||||
|
val responseData = String.format("%s: Invalid response.", method)
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, responseData)
|
||||||
|
return responseData
|
||||||
|
}
|
||||||
|
val contents = response.rawContent
|
||||||
|
return if (contents.size > 0) {
|
||||||
|
if (contents.size >= expectedLength) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Content: %s", method, ByteUtil.shortHexString(contents)))
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val responseData = String.format(
|
||||||
|
"%s: Cannot return data. Data is too short [expected=%s, received=%s].", method, ""
|
||||||
|
+ expectedLength, "" + contents.size)
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, responseData)
|
||||||
|
responseData
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val responseData = String.format("%s: Cannot return data. Zero length response.", method)
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, responseData)
|
||||||
|
responseData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUMP SPECIFIC COMMANDS
|
||||||
|
fun getRemainingInsulin(): Double? {
|
||||||
|
return sendAndGetResponseWithCheck(MedtronicCommandType.GetRemainingInsulin) { _, _, rawContent ->
|
||||||
|
medtronicConverter.decodeRemainingInsulin(rawContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPumpModel(): MedtronicDeviceType? {
|
||||||
|
return sendAndGetResponseWithCheck(MedtronicCommandType.PumpModel) { _, _, rawContent ->
|
||||||
|
medtronicConverter.decodeModel(rawContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBasalProfile(): BasalProfile? {
|
||||||
|
|
||||||
|
// wakeUp
|
||||||
|
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
|
||||||
|
val commandType = MedtronicCommandType.GetBasalProfileSTD
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: $commandType")
|
||||||
|
medtronicUtil.setCurrentCommand(commandType)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
|
||||||
|
for (retries in 0..MAX_COMMAND_TRIES) {
|
||||||
|
try {
|
||||||
|
// create message
|
||||||
|
var msg: PumpMessage
|
||||||
|
msg = makePumpMessage(commandType)
|
||||||
|
|
||||||
|
// send and wait for response
|
||||||
|
var response = sendAndListen(msg, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
|
||||||
|
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getRawContent()));
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getMessageBody().getTxData()));
|
||||||
|
val check = checkResponseContent(response, commandType.commandDescription, 1)
|
||||||
|
var data: ByteArray = byteArrayOf()
|
||||||
|
if (check == null) {
|
||||||
|
data = response.rawContentOfFrame
|
||||||
|
val ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, PumpAckMessageBody())
|
||||||
|
while (checkIfWeHaveMoreData(commandType, response, data)) {
|
||||||
|
response = sendAndListen(ackMsg, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
|
||||||
|
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs, HexDump.toHexStringDisplayable(response2.getRawContent()));
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs,
|
||||||
|
// HexDump.toHexStringDisplayable(response2.getMessageBody().getTxData()));
|
||||||
|
val check2 = checkResponseContent(response, commandType.commandDescription, 1)
|
||||||
|
if (check2 == null) {
|
||||||
|
data = ByteUtil.concat(data, response.rawContentOfFrame)
|
||||||
|
} else {
|
||||||
|
errorResponse = check2
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Error with response got GetProfile: $check2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorResponse = check
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "End Response: {}", ByteUtil.getHex(data))
|
||||||
|
|
||||||
|
var basalProfile: BasalProfile? = medtronicConverter.decodeBasalProfile(medtronicPumpPlugin.pumpDescription.pumpType, data)
|
||||||
|
// checkResponseRawContent(data, commandType) {
|
||||||
|
// basalProfile = medtronicConverter.decodeBasalProfile(medtronicPumpPlugin.pumpDescription.pumpType, data)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (basalProfile != null) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name, basalProfile))
|
||||||
|
medtronicUtil.setCurrentCommand(null)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return basalProfile
|
||||||
|
}
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Error reading profile in max retries.")
|
||||||
|
medtronicUtil.setCurrentCommand(null)
|
||||||
|
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkIfWeHaveMoreData(commandType: MedtronicCommandType, response: PumpMessage, data: ByteArray): Boolean {
|
||||||
|
if (commandType === MedtronicCommandType.GetBasalProfileSTD || //
|
||||||
|
commandType === MedtronicCommandType.GetBasalProfileA || //
|
||||||
|
commandType === MedtronicCommandType.GetBasalProfileB) {
|
||||||
|
val responseRaw = response.rawContentOfFrame
|
||||||
|
val last = responseRaw.size - 1
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Length: " + data.size)
|
||||||
|
if (data.size >= BasalProfile.MAX_RAW_DATA_SIZE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return if (responseRaw.size < 2) {
|
||||||
|
false
|
||||||
|
} else !(responseRaw[last] == 0x00.toByte() && responseRaw[last - 1] == 0x00.toByte() && responseRaw[last - 2] == 0x00.toByte())
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPumpTime(): ClockDTO? {
|
||||||
|
val localTime = LocalDateTime()
|
||||||
|
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRealTimeClock) { _, _, rawContent ->
|
||||||
|
medtronicConverter.decodeTime(rawContent)
|
||||||
|
}
|
||||||
|
if (responseObject != null) {
|
||||||
|
return ClockDTO(localDeviceTime = localTime, pumpTime = responseObject)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTemporaryBasal(): TempBasalPair? {
|
||||||
|
return sendAndGetResponseWithCheck(MedtronicCommandType.ReadTemporaryBasal) { _, _, rawContent ->
|
||||||
|
TempBasalPair(aapsLogger, rawContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPumpSettings(): Map<String, PumpSettingDTO>? {
|
||||||
|
return sendAndGetResponseWithCheck(getSettings(medtronicUtil.medtronicPumpModel)) { _, _, rawContent ->
|
||||||
|
medtronicConverter.decodeSettingsLoop(rawContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBolus(units: Double): Boolean {
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "setBolus: $units")
|
||||||
|
return setCommand(MedtronicCommandType.SetBolus, medtronicUtil.getBolusStrokes(units))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTemporaryBasal(tbr: TempBasalPair): Boolean {
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "setTBR: " + tbr.description)
|
||||||
|
return setCommand(MedtronicCommandType.SetTemporaryBasal, tbr.asRawData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPumpTime(): Boolean {
|
||||||
|
val gc = GregorianCalendar()
|
||||||
|
gc.add(Calendar.SECOND, 5)
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "setPumpTime: " + DateTimeUtil.toString(gc))
|
||||||
|
val yearByte = getByteArrayFromUnsignedShort(gc[Calendar.YEAR], true)
|
||||||
|
// val i = 1
|
||||||
|
// val data = ByteArray(8)
|
||||||
|
// data[0] = 7
|
||||||
|
// data[i] = gc[Calendar.HOUR_OF_DAY].toByte()
|
||||||
|
// data[i + 1] = gc[Calendar.MINUTE].toByte()
|
||||||
|
// data[i + 2] = gc[Calendar.SECOND].toByte()
|
||||||
|
// val yearByte = getByteArrayFromUnsignedShort(gc[Calendar.YEAR], true)
|
||||||
|
// data[i + 3] = yearByte[0]
|
||||||
|
// data[i + 4] = yearByte[1]
|
||||||
|
// data[i + 5] = (gc[Calendar.MONTH] + 1).toByte()
|
||||||
|
// data[i + 6] = gc[Calendar.DAY_OF_MONTH].toByte()
|
||||||
|
|
||||||
|
val timeData = byteArrayOf(
|
||||||
|
7,
|
||||||
|
gc[Calendar.HOUR_OF_DAY].toByte(),
|
||||||
|
gc[Calendar.MINUTE].toByte(),
|
||||||
|
gc[Calendar.SECOND].toByte(),
|
||||||
|
yearByte[0],
|
||||||
|
yearByte[1],
|
||||||
|
(gc[Calendar.MONTH] + 1).toByte(),
|
||||||
|
gc[Calendar.DAY_OF_MONTH].toByte()
|
||||||
|
)
|
||||||
|
|
||||||
|
//aapsLogger.info(LTag.PUMPCOMM,"setPumpTime: Body: " + ByteUtil.getHex(data));
|
||||||
|
return setCommand(MedtronicCommandType.SetRealTimeClock, timeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCommand(commandType: MedtronicCommandType, body: ByteArray): Boolean {
|
||||||
|
for (retries in 0..MAX_COMMAND_TRIES) {
|
||||||
|
try {
|
||||||
|
if (doWakeUpBeforeCommand) wakeUp(false)
|
||||||
|
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Body - %s", commandType.commandDescription,
|
||||||
|
ByteUtil.getHex(body)))
|
||||||
|
val msg = makePumpMessage(commandType, CarelinkLongMessageBody(body))
|
||||||
|
val pumpMessage = runCommandWithArgs(msg)
|
||||||
|
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: %s", commandType.commandDescription, pumpMessage.responseContent))
|
||||||
|
if (pumpMessage.commandType === MedtronicCommandType.CommandACK) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "We received non-ACK response from pump: " + pumpMessage.responseContent)
|
||||||
|
}
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelTBR(): Boolean {
|
||||||
|
return setTemporaryBasal(TempBasalPair(0.0, false, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRemainingBattery(): BatteryStatusDTO? {
|
||||||
|
return sendAndGetResponseWithCheck(MedtronicCommandType.GetBatteryStatus) { _, _, rawContent ->
|
||||||
|
medtronicConverter.decodeBatteryStatus(rawContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBasalProfile(basalProfile: BasalProfile): Boolean {
|
||||||
|
val basalProfileFrames = medtronicUtil.getBasalProfileFrames(basalProfile.rawData)
|
||||||
|
for (retries in 0..MAX_COMMAND_TRIES) {
|
||||||
|
var responseMessage: PumpMessage? = null
|
||||||
|
try {
|
||||||
|
responseMessage = runCommandWithFrames(MedtronicCommandType.SetBasalProfileSTD,
|
||||||
|
basalProfileFrames)
|
||||||
|
if (responseMessage!!.commandType === MedtronicCommandType.CommandACK) return true
|
||||||
|
} catch (e: RileyLinkCommunicationException) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
|
||||||
|
}
|
||||||
|
if (responseMessage != null) aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Set Basal Profile: Invalid response: commandType=%s,rawData=%s", responseMessage.commandType, ByteUtil.shortHexString(responseMessage.rawContent))) else aapsLogger.warn(LTag.PUMPCOMM, "Set Basal Profile: Null response.")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,444 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm;
|
|
||||||
|
|
||||||
import org.joda.time.IllegalFieldValueException;
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 5/9/18.
|
|
||||||
* High level decoder for data returned through MedtroniUIComm
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class MedtronicConverter {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
private final MedtronicUtil medtronicUtil;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MedtronicConverter(
|
|
||||||
AAPSLogger aapsLogger,
|
|
||||||
MedtronicUtil medtronicUtil
|
|
||||||
) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.medtronicUtil = medtronicUtil;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object convertResponse(PumpType pumpType, MedtronicCommandType commandType, byte[] rawContent) {
|
|
||||||
|
|
||||||
if ((rawContent == null || rawContent.length < 1) && commandType != MedtronicCommandType.PumpModel) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Content is empty or too short, no data to convert (type=%s,isNull=%b,length=%s)",
|
|
||||||
commandType.name(), rawContent == null, rawContent == null ? "-" : rawContent.length));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Raw response before convert: " + ByteUtil.shortHexString(rawContent));
|
|
||||||
|
|
||||||
switch (commandType) {
|
|
||||||
|
|
||||||
case PumpModel: {
|
|
||||||
return decodeModel(rawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetRealTimeClock: {
|
|
||||||
return decodeTime(rawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetRemainingInsulin: {
|
|
||||||
return decodeRemainingInsulin(rawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetBatteryStatus: {
|
|
||||||
return decodeBatteryStatus(rawContent); // 1
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetBasalProfileSTD:
|
|
||||||
case GetBasalProfileA:
|
|
||||||
case GetBasalProfileB: {
|
|
||||||
return decodeBasalProfile(pumpType, rawContent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
case ReadTemporaryBasal: {
|
|
||||||
return new TempBasalPair(aapsLogger, rawContent); // 5
|
|
||||||
}
|
|
||||||
|
|
||||||
case Settings_512: {
|
|
||||||
return decodeSettingsLoop(rawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
case Settings: {
|
|
||||||
return decodeSettingsLoop(rawContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
case SetBolus: {
|
|
||||||
return rawContent; // 1
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
throw new RuntimeException("Unsupported command Type: " + commandType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private BasalProfile decodeBasalProfile(PumpType pumpType, byte[] rawContent) {
|
|
||||||
|
|
||||||
BasalProfile basalProfile = new BasalProfile(aapsLogger, rawContent);
|
|
||||||
|
|
||||||
return basalProfile.verify(pumpType) ? basalProfile : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private MedtronicDeviceType decodeModel(byte[] rawContent) {
|
|
||||||
|
|
||||||
if ((rawContent == null || rawContent.length < 4)) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Error reading PumpModel, returning Unknown_Device");
|
|
||||||
return MedtronicDeviceType.Unknown_Device;
|
|
||||||
}
|
|
||||||
|
|
||||||
String rawModel = StringUtil.fromBytes(ByteUtil.substring(rawContent, 1, 3));
|
|
||||||
MedtronicDeviceType pumpModel = MedtronicDeviceType.getByDescription(rawModel);
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "PumpModel: [raw=%s, resolved=%s]", rawModel, pumpModel.name()));
|
|
||||||
|
|
||||||
if (pumpModel != MedtronicDeviceType.Unknown_Device) {
|
|
||||||
if (!medtronicUtil.isModelSet()) {
|
|
||||||
medtronicUtil.setMedtronicPumpModel(pumpModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pumpModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private BatteryStatusDTO decodeBatteryStatus(byte[] rawData) {
|
|
||||||
// 00 7C 00 00
|
|
||||||
|
|
||||||
BatteryStatusDTO batteryStatus = new BatteryStatusDTO();
|
|
||||||
|
|
||||||
int status = rawData[0];
|
|
||||||
|
|
||||||
if (status == 0) {
|
|
||||||
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Normal;
|
|
||||||
} else if (status == 1) {
|
|
||||||
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Low;
|
|
||||||
} else if (status == 2) {
|
|
||||||
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawData.length > 1) {
|
|
||||||
|
|
||||||
Double d = null;
|
|
||||||
|
|
||||||
// if response in 3 bytes then we add additional information
|
|
||||||
if (rawData.length == 2) {
|
|
||||||
d = (rawData[1] * 1.0d) / 100.0d;
|
|
||||||
} else {
|
|
||||||
d = (ByteUtil.toInt(rawData[1], rawData[2]) * 1.0d) / 100.0d;
|
|
||||||
}
|
|
||||||
|
|
||||||
batteryStatus.voltage = d;
|
|
||||||
batteryStatus.extendedDataReceived = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return batteryStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Float decodeRemainingInsulin(byte[] rawData) {
|
|
||||||
int startIdx = 0;
|
|
||||||
|
|
||||||
MedtronicDeviceType pumpModel = medtronicUtil.getMedtronicPumpModel();
|
|
||||||
|
|
||||||
int strokes = pumpModel == null ? 10 : pumpModel.getBolusStrokes();
|
|
||||||
|
|
||||||
if (strokes == 40) {
|
|
||||||
startIdx = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int reqLength = startIdx + 1;
|
|
||||||
float value = 0;
|
|
||||||
|
|
||||||
if (reqLength >= rawData.length) {
|
|
||||||
value = rawData[startIdx] / (1.0f * strokes);
|
|
||||||
} else {
|
|
||||||
value = ByteUtil.toInt(rawData[startIdx], rawData[startIdx + 1]) / (1.0f * strokes);
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Remaining insulin: " + value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private LocalDateTime decodeTime(byte[] rawContent) {
|
|
||||||
|
|
||||||
int hours = ByteUtil.asUINT8(rawContent[0]);
|
|
||||||
int minutes = ByteUtil.asUINT8(rawContent[1]);
|
|
||||||
int seconds = ByteUtil.asUINT8(rawContent[2]);
|
|
||||||
int year = (ByteUtil.asUINT8(rawContent[4]) & 0x3f) + 1984;
|
|
||||||
int month = ByteUtil.asUINT8(rawContent[5]);
|
|
||||||
int day = ByteUtil.asUINT8(rawContent[6]);
|
|
||||||
try {
|
|
||||||
LocalDateTime pumpTime = new LocalDateTime(year, month, day, hours, minutes, seconds);
|
|
||||||
return pumpTime;
|
|
||||||
} catch (IllegalFieldValueException e) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM,
|
|
||||||
String.format(Locale.ENGLISH, "decodeTime: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d",
|
|
||||||
year, month, day, hours, minutes, seconds));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Map<String, PumpSettingDTO> decodeSettingsLoop(byte[] rd) {
|
|
||||||
|
|
||||||
Map<String, PumpSettingDTO> map = new HashMap<>();
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map);
|
|
||||||
addSettingToMap(
|
|
||||||
"PCFG_MAX_BASAL",
|
|
||||||
""
|
|
||||||
+ decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()],
|
|
||||||
rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map);
|
|
||||||
addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h",
|
|
||||||
PumpConfigurationGroup.General, map);
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
if (rd[10] == 1) {
|
|
||||||
String patt;
|
|
||||||
switch (rd[11]) {
|
|
||||||
case 0:
|
|
||||||
patt = "STD";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
patt = "A";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
patt = "B";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
patt = "???";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", "STD", PumpConfigurationGroup.Basal, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Map<String, PumpSettingDTO> decodeSettings512(byte[] rd) {
|
|
||||||
|
|
||||||
Map<String, PumpSettingDTO> map = new HashMap<>();
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_AUTOOFF_TIMEOUT", "" + rd[0], PumpConfigurationGroup.General, map);
|
|
||||||
|
|
||||||
if (rd[1] == 4) {
|
|
||||||
addSettingToMap("PCFG_ALARM_MODE", "Silent", PumpConfigurationGroup.Sound, map);
|
|
||||||
} else {
|
|
||||||
addSettingToMap("PCFG_ALARM_MODE", "Normal", PumpConfigurationGroup.Sound, map);
|
|
||||||
addSettingToMap("PCFG_ALARM_BEEP_VOLUME", "" + rd[1], PumpConfigurationGroup.Sound, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_AUDIO_BOLUS_ENABLED", parseResultEnable(rd[2]), PumpConfigurationGroup.Bolus, map);
|
|
||||||
|
|
||||||
if (rd[2] == 1) {
|
|
||||||
addSettingToMap("PCFG_AUDIO_BOLUS_STEP_SIZE", "" + decodeBolusInsulin(ByteUtil.asUINT8(rd[3])),
|
|
||||||
PumpConfigurationGroup.Bolus, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_VARIABLE_BOLUS_ENABLED", parseResultEnable(rd[4]), PumpConfigurationGroup.Bolus, map);
|
|
||||||
addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map);
|
|
||||||
addSettingToMap(
|
|
||||||
"PCFG_MAX_BASAL",
|
|
||||||
""
|
|
||||||
+ decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()],
|
|
||||||
rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map);
|
|
||||||
addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h",
|
|
||||||
PumpConfigurationGroup.General, map);
|
|
||||||
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) {
|
|
||||||
addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] == 0 ? 50 : 100), PumpConfigurationGroup.Insulin,
|
|
||||||
map);
|
|
||||||
// LOG.debug("Insulin concentration: " + rd[9]);
|
|
||||||
} else {
|
|
||||||
addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] != 0 ? 50 : 100), PumpConfigurationGroup.Insulin,
|
|
||||||
map);
|
|
||||||
// LOG.debug("Insulin concentration: " + rd[9]);
|
|
||||||
}
|
|
||||||
addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
if (rd[10] == 1) {
|
|
||||||
String patt;
|
|
||||||
switch (rd[11]) {
|
|
||||||
case 0:
|
|
||||||
patt = "STD";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
patt = "A";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
patt = "B";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
patt = "???";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("CFG_MM_RF_ENABLED", parseResultEnable(rd[12]), PumpConfigurationGroup.General, map);
|
|
||||||
addSettingToMap("CFG_MM_BLOCK_ENABLED", parseResultEnable(rd[13]), PumpConfigurationGroup.General, map);
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map);
|
|
||||||
|
|
||||||
if (rd[14] == 1) {
|
|
||||||
addSettingToMap("PCFG_TEMP_BASAL_PERCENT", "" + rd[15], PumpConfigurationGroup.Basal, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("CFG_PARADIGM_LINK_ENABLE", parseResultEnable(rd[16]), PumpConfigurationGroup.General, map);
|
|
||||||
|
|
||||||
decodeInsulinActionSetting(rd, map);
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void addSettingToMap(String key, String value, PumpConfigurationGroup group, Map<String, PumpSettingDTO> map) {
|
|
||||||
map.put(key, new PumpSettingDTO(key, value, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, PumpSettingDTO> decodeSettings(byte[] rd) {
|
|
||||||
Map<String, PumpSettingDTO> map = decodeSettings512(rd);
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_MM_RESERVOIR_WARNING_TYPE_TIME", rd[18] != 0 ? "PCFG_MM_RESERVOIR_WARNING_TYPE_TIME"
|
|
||||||
: "PCFG_MM_RESERVOIR_WARNING_TYPE_UNITS", PumpConfigurationGroup.Other, map);
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_MM_SRESERVOIR_WARNING_POINT", "" + ByteUtil.asUINT8(rd[19]),
|
|
||||||
PumpConfigurationGroup.Other, map);
|
|
||||||
|
|
||||||
addSettingToMap("CFG_MM_KEYPAD_LOCKED", parseResultEnable(rd[20]), PumpConfigurationGroup.Other, map);
|
|
||||||
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) {
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_BOLUS_SCROLL_STEP_SIZE", "" + rd[21], PumpConfigurationGroup.Bolus, map);
|
|
||||||
addSettingToMap("PCFG_CAPTURE_EVENT_ENABLE", parseResultEnable(rd[22]), PumpConfigurationGroup.Other, map);
|
|
||||||
addSettingToMap("PCFG_OTHER_DEVICE_ENABLE", parseResultEnable(rd[23]), PumpConfigurationGroup.Other, map);
|
|
||||||
addSettingToMap("PCFG_OTHER_DEVICE_PAIRED_STATE", parseResultEnable(rd[24]), PumpConfigurationGroup.Other,
|
|
||||||
map);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String parseResultEnable(int i) {
|
|
||||||
switch (i) {
|
|
||||||
case 0:
|
|
||||||
return "No";
|
|
||||||
case 1:
|
|
||||||
return "Yes";
|
|
||||||
default:
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private float getStrokesPerUnit(boolean isBasal) {
|
|
||||||
return isBasal ? 40.0f : 10; // pumpModel.getBolusStrokes();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 512
|
|
||||||
private void decodeInsulinActionSetting(byte[] ai, Map<String, PumpSettingDTO> map) {
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_512_712)) {
|
|
||||||
addSettingToMap("PCFG_INSULIN_ACTION_TYPE", (ai[17] != 0 ? "Regular" : "Fast"),
|
|
||||||
PumpConfigurationGroup.Insulin, map);
|
|
||||||
} else {
|
|
||||||
int i = ai[17];
|
|
||||||
String s;
|
|
||||||
|
|
||||||
if ((i == 0) || (i == 1)) {
|
|
||||||
s = ai[17] != 0 ? "Regular" : "Fast";
|
|
||||||
} else {
|
|
||||||
if (i == 15)
|
|
||||||
s = "Unset";
|
|
||||||
else
|
|
||||||
s = "Curve: " + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
addSettingToMap("PCFG_INSULIN_ACTION_TYPE", s, PumpConfigurationGroup.Insulin, map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private double decodeBasalInsulin(int i) {
|
|
||||||
return (double) i / (double) getStrokesPerUnit(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private double decodeBolusInsulin(int i) {
|
|
||||||
|
|
||||||
return (double) i / (double) getStrokesPerUnit(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int getSettingIndexMaxBasal() {
|
|
||||||
return is523orHigher() ? 7 : 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int getSettingIndexTimeDisplayFormat() {
|
|
||||||
return is523orHigher() ? 9 : 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private double decodeMaxBolus(byte[] ai) {
|
|
||||||
return is523orHigher() ? decodeBolusInsulin(ByteUtil.toInt(ai[5], ai[6])) : decodeBolusInsulin(ByteUtil
|
|
||||||
.asUINT8(ai[5]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean is523orHigher() {
|
|
||||||
return (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import org.joda.time.IllegalFieldValueException
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 5/9/18.
|
||||||
|
* High level decoder for data returned through MedtroniUIComm
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class MedtronicConverter @Inject constructor(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val medtronicUtil: MedtronicUtil
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun decodeBasalProfile(pumpType: PumpType, rawContent: ByteArray): BasalProfile? {
|
||||||
|
val basalProfile = BasalProfile(aapsLogger, rawContent)
|
||||||
|
return if (basalProfile.verify(pumpType)) basalProfile else null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeModel(rawContent: ByteArray): MedtronicDeviceType {
|
||||||
|
if (rawContent.size < 4) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Error reading PumpModel, returning Unknown_Device")
|
||||||
|
return MedtronicDeviceType.Unknown_Device
|
||||||
|
}
|
||||||
|
val rawModel = StringUtil.fromBytes(ByteUtil.substring(rawContent, 1, 3))
|
||||||
|
val pumpModel = MedtronicDeviceType.getByDescription(rawModel)
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "PumpModel: [raw=%s, resolved=%s]", rawModel, pumpModel.name))
|
||||||
|
if (pumpModel != MedtronicDeviceType.Unknown_Device) {
|
||||||
|
if (!medtronicUtil.isModelSet) {
|
||||||
|
medtronicUtil.medtronicPumpModel = pumpModel
|
||||||
|
medtronicUtil.isModelSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pumpModel
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeBatteryStatus(rawData: ByteArray): BatteryStatusDTO {
|
||||||
|
// 00 7C 00 00
|
||||||
|
val batteryStatus = BatteryStatusDTO()
|
||||||
|
val status = rawData[0].toInt()
|
||||||
|
if (status == 0) {
|
||||||
|
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Normal
|
||||||
|
} else if (status == 1) {
|
||||||
|
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Low
|
||||||
|
} else if (status == 2) {
|
||||||
|
batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Unknown
|
||||||
|
}
|
||||||
|
if (rawData.size > 1) {
|
||||||
|
var d: Double? //= null
|
||||||
|
|
||||||
|
// if response in 3 bytes then we add additional information
|
||||||
|
d = if (rawData.size == 2) {
|
||||||
|
rawData[1] * 1.0 / 100.0
|
||||||
|
} else {
|
||||||
|
ByteUtil.toInt(rawData[1], rawData[2]) * 1.0 / 100.0
|
||||||
|
}
|
||||||
|
batteryStatus.voltage = d
|
||||||
|
batteryStatus.extendedDataReceived = true
|
||||||
|
}
|
||||||
|
return batteryStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeRemainingInsulin(rawData: ByteArray): Double {
|
||||||
|
var startIdx = 0
|
||||||
|
val pumpModel = medtronicUtil.medtronicPumpModel
|
||||||
|
val strokes = pumpModel.bolusStrokes //?: 10
|
||||||
|
if (strokes == 40) {
|
||||||
|
startIdx = 2
|
||||||
|
}
|
||||||
|
val reqLength = startIdx + 1
|
||||||
|
val value: Double
|
||||||
|
value = if (reqLength >= rawData.size) {
|
||||||
|
rawData[startIdx] / (1.0 * strokes)
|
||||||
|
} else {
|
||||||
|
ByteUtil.toInt(rawData[startIdx], rawData[startIdx + 1]) / (1.0 * strokes)
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Remaining insulin: $value")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeTime(rawContent: ByteArray): LocalDateTime? {
|
||||||
|
val hours = ByteUtil.asUINT8(rawContent[0])
|
||||||
|
val minutes = ByteUtil.asUINT8(rawContent[1])
|
||||||
|
val seconds = ByteUtil.asUINT8(rawContent[2])
|
||||||
|
val year = (ByteUtil.asUINT8(rawContent[4]) and 0x3f) + 1984
|
||||||
|
val month = ByteUtil.asUINT8(rawContent[5])
|
||||||
|
val day = ByteUtil.asUINT8(rawContent[6])
|
||||||
|
return try {
|
||||||
|
LocalDateTime(year, month, day, hours, minutes, seconds)
|
||||||
|
} catch (e: IllegalFieldValueException) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "decodeTime: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d",
|
||||||
|
year, month, day, hours, minutes, seconds))
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun decodeSettingsLoop(rd: ByteArray): Map<String, PumpSettingDTO> {
|
||||||
|
val map: MutableMap<String, PumpSettingDTO> = HashMap()
|
||||||
|
addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map)
|
||||||
|
addSettingToMap(
|
||||||
|
"PCFG_MAX_BASAL", ""
|
||||||
|
+ decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[settingIndexMaxBasal].toInt(),
|
||||||
|
rd[settingIndexMaxBasal + 1].toInt())), PumpConfigurationGroup.Basal, map)
|
||||||
|
addSettingToMap("CFG_BASE_CLOCK_MODE", if (rd[settingIndexTimeDisplayFormat].toInt() == 0) "12h" else "24h",
|
||||||
|
PumpConfigurationGroup.General, map)
|
||||||
|
addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10].toInt()), PumpConfigurationGroup.Basal, map)
|
||||||
|
if (rd[10].toInt() == 1) {
|
||||||
|
val patt: String
|
||||||
|
patt = when (rd[11].toInt()) {
|
||||||
|
0 -> "STD"
|
||||||
|
1 -> "A"
|
||||||
|
2 -> "B"
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map)
|
||||||
|
} else {
|
||||||
|
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", "STD", PumpConfigurationGroup.Basal, map)
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_TEMP_BASAL_TYPE", if (rd[14].toInt() != 0) "Percent" else "Units", PumpConfigurationGroup.Basal, map)
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSettings512(rd: ByteArray): MutableMap<String, PumpSettingDTO> {
|
||||||
|
val map: MutableMap<String, PumpSettingDTO> = HashMap()
|
||||||
|
addSettingToMap("PCFG_AUTOOFF_TIMEOUT", "" + rd[0], PumpConfigurationGroup.General, map)
|
||||||
|
if (rd[1].toInt() == 4) {
|
||||||
|
addSettingToMap("PCFG_ALARM_MODE", "Silent", PumpConfigurationGroup.Sound, map)
|
||||||
|
} else {
|
||||||
|
addSettingToMap("PCFG_ALARM_MODE", "Normal", PumpConfigurationGroup.Sound, map)
|
||||||
|
addSettingToMap("PCFG_ALARM_BEEP_VOLUME", "" + rd[1], PumpConfigurationGroup.Sound, map)
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_AUDIO_BOLUS_ENABLED", parseResultEnable(rd[2].toInt()), PumpConfigurationGroup.Bolus, map)
|
||||||
|
if (rd[2].toInt() == 1) {
|
||||||
|
addSettingToMap("PCFG_AUDIO_BOLUS_STEP_SIZE", "" + decodeBolusInsulin(ByteUtil.asUINT8(rd[3])),
|
||||||
|
PumpConfigurationGroup.Bolus, map)
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_VARIABLE_BOLUS_ENABLED", parseResultEnable(rd[4].toInt()), PumpConfigurationGroup.Bolus, map)
|
||||||
|
addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map)
|
||||||
|
addSettingToMap(
|
||||||
|
"PCFG_MAX_BASAL", ""
|
||||||
|
+ decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[settingIndexMaxBasal].toInt(),
|
||||||
|
rd[settingIndexMaxBasal + 1].toInt())), PumpConfigurationGroup.Basal, map)
|
||||||
|
addSettingToMap("CFG_BASE_CLOCK_MODE", if (rd[settingIndexTimeDisplayFormat].toInt() == 0) "12h" else "24h",
|
||||||
|
PumpConfigurationGroup.General, map)
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) {
|
||||||
|
addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + if (rd[9].toInt() == 0) 50 else 100, PumpConfigurationGroup.Insulin,
|
||||||
|
map)
|
||||||
|
// LOG.debug("Insulin concentration: " + rd[9]);
|
||||||
|
} else {
|
||||||
|
addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + if (rd[9].toInt() != 0) 50 else 100, PumpConfigurationGroup.Insulin,
|
||||||
|
map)
|
||||||
|
// LOG.debug("Insulin concentration: " + rd[9]);
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10].toInt()), PumpConfigurationGroup.Basal, map)
|
||||||
|
if (rd[10].toInt() == 1) {
|
||||||
|
val patt: String
|
||||||
|
patt = when (rd[11].toInt()) {
|
||||||
|
0 -> "STD"
|
||||||
|
1 -> "A"
|
||||||
|
2 -> "B"
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map)
|
||||||
|
}
|
||||||
|
addSettingToMap("CFG_MM_RF_ENABLED", parseResultEnable(rd[12].toInt()), PumpConfigurationGroup.General, map)
|
||||||
|
addSettingToMap("CFG_MM_BLOCK_ENABLED", parseResultEnable(rd[13].toInt()), PumpConfigurationGroup.General, map)
|
||||||
|
addSettingToMap("PCFG_TEMP_BASAL_TYPE", if (rd[14].toInt() != 0) "Percent" else "Units", PumpConfigurationGroup.Basal, map)
|
||||||
|
if (rd[14].toInt() == 1) {
|
||||||
|
addSettingToMap("PCFG_TEMP_BASAL_PERCENT", "" + rd[15], PumpConfigurationGroup.Basal, map)
|
||||||
|
}
|
||||||
|
addSettingToMap("CFG_PARADIGM_LINK_ENABLE", parseResultEnable(rd[16].toInt()), PumpConfigurationGroup.General, map)
|
||||||
|
decodeInsulinActionSetting(rd, map)
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addSettingToMap(key: String, value: String, group: PumpConfigurationGroup, map: MutableMap<String, PumpSettingDTO>) {
|
||||||
|
map[key] = PumpSettingDTO(key, value, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeSettings(rd: ByteArray): Map<String, PumpSettingDTO> {
|
||||||
|
val map = decodeSettings512(rd)
|
||||||
|
addSettingToMap("PCFG_MM_RESERVOIR_WARNING_TYPE_TIME", if (rd[18].toInt() != 0) "PCFG_MM_RESERVOIR_WARNING_TYPE_TIME" else "PCFG_MM_RESERVOIR_WARNING_TYPE_UNITS", PumpConfigurationGroup.Other, map)
|
||||||
|
addSettingToMap("PCFG_MM_SRESERVOIR_WARNING_POINT", "" + ByteUtil.asUINT8(rd[19]),
|
||||||
|
PumpConfigurationGroup.Other, map)
|
||||||
|
addSettingToMap("CFG_MM_KEYPAD_LOCKED", parseResultEnable(rd[20].toInt()), PumpConfigurationGroup.Other, map)
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) {
|
||||||
|
addSettingToMap("PCFG_BOLUS_SCROLL_STEP_SIZE", "" + rd[21], PumpConfigurationGroup.Bolus, map)
|
||||||
|
addSettingToMap("PCFG_CAPTURE_EVENT_ENABLE", parseResultEnable(rd[22].toInt()), PumpConfigurationGroup.Other, map)
|
||||||
|
addSettingToMap("PCFG_OTHER_DEVICE_ENABLE", parseResultEnable(rd[23].toInt()), PumpConfigurationGroup.Other, map)
|
||||||
|
addSettingToMap("PCFG_OTHER_DEVICE_PAIRED_STATE", parseResultEnable(rd[24].toInt()), PumpConfigurationGroup.Other,
|
||||||
|
map)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseResultEnable(i: Int): String {
|
||||||
|
return when (i) {
|
||||||
|
0 -> "No"
|
||||||
|
1 -> "Yes"
|
||||||
|
else -> "???"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStrokesPerUnit(isBasal: Boolean): Float {
|
||||||
|
return if (isBasal) 40.0f else 10.0f // pumpModel.getBolusStrokes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 512
|
||||||
|
private fun decodeInsulinActionSetting(ai: ByteArray, map: MutableMap<String, PumpSettingDTO>) {
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_512_712)) {
|
||||||
|
addSettingToMap("PCFG_INSULIN_ACTION_TYPE", if (ai[17].toInt() != 0) "Regular" else "Fast",
|
||||||
|
PumpConfigurationGroup.Insulin, map)
|
||||||
|
} else {
|
||||||
|
val i = ai[17].toInt()
|
||||||
|
val s: String
|
||||||
|
s = if (i == 0 || i == 1) {
|
||||||
|
if (ai[17].toInt() != 0) "Regular" else "Fast"
|
||||||
|
} else {
|
||||||
|
if (i == 15) "Unset" else "Curve: $i"
|
||||||
|
}
|
||||||
|
addSettingToMap("PCFG_INSULIN_ACTION_TYPE", s, PumpConfigurationGroup.Insulin, map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBasalInsulin(i: Int): Double {
|
||||||
|
return i.toDouble() / getStrokesPerUnit(true).toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBolusInsulin(i: Int): Double {
|
||||||
|
return i.toDouble() / getStrokesPerUnit(false).toDouble()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val settingIndexMaxBasal: Int
|
||||||
|
get() = if (is523orHigher()) 7 else 6
|
||||||
|
|
||||||
|
private val settingIndexTimeDisplayFormat: Int
|
||||||
|
get() = if (is523orHigher()) 9 else 8
|
||||||
|
|
||||||
|
private fun decodeMaxBolus(ai: ByteArray): Double {
|
||||||
|
return if (is523orHigher())
|
||||||
|
decodeBolusInsulin(ByteUtil.toInt(ai[5], ai[6]))
|
||||||
|
else
|
||||||
|
decodeBolusInsulin(ByteUtil.asUINT8(ai[5]))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun is523orHigher(): Boolean {
|
||||||
|
return MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,182 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class MedtronicHistoryDecoder<T extends MedtronicHistoryEntry> implements MedtronicHistoryDecoderInterface<T> {
|
|
||||||
|
|
||||||
@Inject protected AAPSLogger aapsLogger;
|
|
||||||
@Inject protected MedtronicUtil medtronicUtil;
|
|
||||||
|
|
||||||
protected ByteUtil bitUtils;
|
|
||||||
|
|
||||||
// STATISTICS (remove at later time or not)
|
|
||||||
protected boolean statisticsEnabled = true;
|
|
||||||
protected Map<Integer, Integer> unknownOpCodes;
|
|
||||||
protected Map<RecordDecodeStatus, Map<String, String>> mapStatistics;
|
|
||||||
|
|
||||||
|
|
||||||
public MedtronicHistoryDecoder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// public abstract <E extends MedtronicHistoryEntry> Class<E> getHistoryEntryClass();
|
|
||||||
|
|
||||||
// public abstract RecordDecodeStatus decodeRecord(T record);
|
|
||||||
|
|
||||||
public abstract void postProcess();
|
|
||||||
|
|
||||||
|
|
||||||
protected abstract void runPostDecodeTasks();
|
|
||||||
|
|
||||||
|
|
||||||
// TODO_ extend this to also use bigger pages (for now we support only 1024 pages)
|
|
||||||
private List<Byte> checkPage(RawHistoryPage page, boolean partial) throws RuntimeException {
|
|
||||||
List<Byte> byteList = new ArrayList<Byte>();
|
|
||||||
|
|
||||||
// if (!partial && page.getData().length != 1024 /* page.commandType.getRecordLength() */) {
|
|
||||||
// LOG.error("Page size is not correct. Size should be {}, but it was {} instead.", 1024,
|
|
||||||
// page.getData().length);
|
|
||||||
// // throw exception perhaps
|
|
||||||
// return byteList;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (medtronicUtil.getMedtronicPumpModel() == null) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "Device Type is not defined.");
|
|
||||||
return byteList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page.getData().length != 1024) {
|
|
||||||
return ByteUtil.getListFromByteArray(page.getData());
|
|
||||||
} else if (page.isChecksumOK()) {
|
|
||||||
return ByteUtil.getListFromByteArray(page.getOnlyData());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<T> processPageAndCreateRecords(RawHistoryPage rawHistoryPage) {
|
|
||||||
return processPageAndCreateRecords(rawHistoryPage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void prepareStatistics() {
|
|
||||||
if (!statisticsEnabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unknownOpCodes = new HashMap<>();
|
|
||||||
mapStatistics = new HashMap<>();
|
|
||||||
|
|
||||||
for (RecordDecodeStatus stat : RecordDecodeStatus.values()) {
|
|
||||||
mapStatistics.put(stat, new HashMap<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void addToStatistics(MedtronicHistoryEntryInterface pumpHistoryEntry, RecordDecodeStatus status, Integer opCode) {
|
|
||||||
if (!statisticsEnabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (opCode != null) {
|
|
||||||
if (!unknownOpCodes.containsKey(opCode)) {
|
|
||||||
unknownOpCodes.put(opCode, opCode);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapStatistics.get(status).containsKey(pumpHistoryEntry.getEntryTypeName())) {
|
|
||||||
mapStatistics.get(status).put(pumpHistoryEntry.getEntryTypeName(), "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void showStatistics() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
for (Map.Entry unknownEntry : unknownOpCodes.entrySet()) {
|
|
||||||
StringUtil.appendToStringBuilder(sb, "" + unknownEntry.getKey(), ", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "STATISTICS OF PUMP DECODE");
|
|
||||||
|
|
||||||
if (unknownOpCodes.size() > 0) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Unknown Op Codes: " + sb.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<RecordDecodeStatus, Map<String, String>> entry : mapStatistics.entrySet()) {
|
|
||||||
sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (entry.getKey() != RecordDecodeStatus.OK) {
|
|
||||||
if (entry.getValue().size() == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entrysub : entry.getValue().entrySet()) {
|
|
||||||
StringUtil.appendToStringBuilder(sb, entrysub.getKey(), ", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
String spaces = StringUtils.repeat(" ", 14 - entry.getKey().name().length());
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, " %s%s - %d. Elements: %s", entry.getKey().name(), spaces, entry.getValue().size(), sb.toString()));
|
|
||||||
} else {
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, " %s - %d", entry.getKey().name(), entry.getValue().size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int getUnsignedByte(byte value) {
|
|
||||||
if (value < 0)
|
|
||||||
return value + 256;
|
|
||||||
else
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected int getUnsignedInt(int value) {
|
|
||||||
if (value < 0)
|
|
||||||
return value + 256;
|
|
||||||
else
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getFormattedFloat(float value, int decimals) {
|
|
||||||
return StringUtil.getFormatedValueUS(value, decimals);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<T> processPageAndCreateRecords(RawHistoryPage rawHistoryPage, boolean partial) {
|
|
||||||
List<Byte> dataClear = checkPage(rawHistoryPage, partial);
|
|
||||||
List<T> records = createRecords(dataClear);
|
|
||||||
|
|
||||||
for (T record : records) {
|
|
||||||
decodeRecord(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
runPostDecodeTasks();
|
|
||||||
|
|
||||||
return records;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
abstract class MedtronicHistoryDecoder<T : MedtronicHistoryEntry?>(var aapsLogger: AAPSLogger,
|
||||||
|
var medtronicUtil: MedtronicUtil,
|
||||||
|
var bitUtils: ByteUtil) : MedtronicHistoryDecoderInterface<T> {
|
||||||
|
|
||||||
|
// STATISTICS (remove at later time or not)
|
||||||
|
protected var statisticsEnabled = true
|
||||||
|
protected var unknownOpCodes: MutableMap<Int, Int?> = mutableMapOf()
|
||||||
|
protected var mapStatistics: MutableMap<RecordDecodeStatus, MutableMap<String, String>> = mutableMapOf()
|
||||||
|
|
||||||
|
abstract fun postProcess()
|
||||||
|
protected abstract fun runPostDecodeTasks()
|
||||||
|
|
||||||
|
// TODO_ extend this to also use bigger pages (for now we support only 1024 pages)
|
||||||
|
@Throws(RuntimeException::class)
|
||||||
|
private fun checkPage(page: RawHistoryPage): MutableList<Byte> {
|
||||||
|
if (!medtronicUtil.isModelSet) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Device Type is not defined.")
|
||||||
|
return mutableListOf()
|
||||||
|
}
|
||||||
|
return if (page.data.size != 1024) {
|
||||||
|
page.data.toMutableList()
|
||||||
|
} else if (page.isChecksumOK) {
|
||||||
|
page.onlyData.toMutableList()
|
||||||
|
} else {
|
||||||
|
mutableListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processPageAndCreateRecords(rawHistoryPage: RawHistoryPage): MutableList<T> {
|
||||||
|
val dataClear = checkPage(rawHistoryPage)
|
||||||
|
val records: MutableList<T> = createRecords(dataClear)
|
||||||
|
for (record in records) {
|
||||||
|
decodeRecord(record)
|
||||||
|
}
|
||||||
|
runPostDecodeTasks()
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun prepareStatistics() {
|
||||||
|
if (!statisticsEnabled) return
|
||||||
|
// unknownOpCodes = HashMap()
|
||||||
|
// mapStatistics = HashMap()
|
||||||
|
for (stat in RecordDecodeStatus.values()) {
|
||||||
|
mapStatistics[stat] = hashMapOf()
|
||||||
|
//(mapStatistics as HashMap<RecordDecodeStatus, MutableMap<String, String>>)[stat] = hashMapOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun addToStatistics(pumpHistoryEntry: MedtronicHistoryEntryInterface, status: RecordDecodeStatus, opCode: Int?) {
|
||||||
|
if (!statisticsEnabled) return
|
||||||
|
if (opCode != null) {
|
||||||
|
if (!unknownOpCodes.containsKey(opCode)) {
|
||||||
|
unknownOpCodes[opCode] = opCode
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!mapStatistics[status]!!.containsKey(pumpHistoryEntry.entryTypeName)) {
|
||||||
|
mapStatistics[status]!!.put(pumpHistoryEntry.entryTypeName, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun showStatistics() {
|
||||||
|
var sb = StringBuilder()
|
||||||
|
for ((key) in unknownOpCodes) {
|
||||||
|
StringUtil.appendToStringBuilder(sb, "" + key, ", ")
|
||||||
|
}
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "STATISTICS OF PUMP DECODE")
|
||||||
|
if (unknownOpCodes.size > 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Unknown Op Codes: $sb")
|
||||||
|
}
|
||||||
|
for ((key, value) in mapStatistics) {
|
||||||
|
sb = StringBuilder()
|
||||||
|
if (key !== RecordDecodeStatus.OK) {
|
||||||
|
if (value.size == 0) continue
|
||||||
|
for ((key1) in value) {
|
||||||
|
StringUtil.appendToStringBuilder(sb, key1, ", ")
|
||||||
|
}
|
||||||
|
val spaces = StringUtils.repeat(" ", 14 - key.name.length)
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, " ${key.name}$spaces - ${value.size}. Elements: ${sb.toString()}")
|
||||||
|
} else {
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, " ${key.name} - ${value.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUnsignedByte(value: Byte): Int {
|
||||||
|
return if (value < 0) value + 256 else value.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getUnsignedInt(value: Int): Int {
|
||||||
|
return if (value < 0) value + 256 else value
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getUnsignedInt(value: Byte): Int {
|
||||||
|
return if (value < 0) value + 256 else value.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFormattedFloat(value: Float, decimals: Int): String {
|
||||||
|
return StringUtil.getFormatedValueUS(value, decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 3/10/19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public interface MedtronicHistoryDecoderInterface<T> {
|
|
||||||
|
|
||||||
RecordDecodeStatus decodeRecord(T record);
|
|
||||||
|
|
||||||
List<T> createRecords(List<Byte> dataClear);
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 3/10/19.
|
||||||
|
*/
|
||||||
|
interface MedtronicHistoryDecoderInterface<T> {
|
||||||
|
|
||||||
|
fun decodeRecord(record: T): RecordDecodeStatus?
|
||||||
|
fun createRecords(dataClearInput: MutableList<Byte>): MutableList<T>
|
||||||
|
}
|
|
@ -1,316 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public abstract class MedtronicHistoryEntry implements MedtronicHistoryEntryInterface {
|
|
||||||
|
|
||||||
protected List<Byte> rawData;
|
|
||||||
|
|
||||||
public static final Logger LOG = StacktraceLoggerWrapper.getLogger(MedtronicHistoryEntry.class);
|
|
||||||
|
|
||||||
protected int[] sizes = new int[3];
|
|
||||||
|
|
||||||
protected byte[] head;
|
|
||||||
protected byte[] datetime;
|
|
||||||
protected byte[] body;
|
|
||||||
|
|
||||||
// protected LocalDateTime dateTime;
|
|
||||||
|
|
||||||
public long id;
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
public String DT;
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
public Long atechDateTime;
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
protected Map<String, Object> decodedData;
|
|
||||||
|
|
||||||
public long phoneDateTime; // time on phone
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pump id that will be used with AAPS object (time * 1000 + historyType (max is FF = 255)
|
|
||||||
*/
|
|
||||||
protected Long pumpId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if history object is already linked to AAPS object (either Treatment, TempBasal or TDD (tdd's
|
|
||||||
* are not actually
|
|
||||||
* linked))
|
|
||||||
*/
|
|
||||||
public boolean linked = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linked object, see linked
|
|
||||||
*/
|
|
||||||
public Object linkedObject = null;
|
|
||||||
|
|
||||||
|
|
||||||
public void setLinkedObject(Object linkedObject) {
|
|
||||||
this.linked = true;
|
|
||||||
this.linkedObject = linkedObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setData(List<Byte> listRawData, boolean doNotProcess) {
|
|
||||||
this.rawData = listRawData;
|
|
||||||
|
|
||||||
// System.out.println("Head: " + sizes[0] + ", dates: " + sizes[1] +
|
|
||||||
// ", body=" + sizes[2]);
|
|
||||||
|
|
||||||
if (doNotProcess)
|
|
||||||
return;
|
|
||||||
|
|
||||||
head = new byte[getHeadLength() - 1];
|
|
||||||
for (int i = 1; i < (getHeadLength()); i++) {
|
|
||||||
head[i - 1] = listRawData.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getDateTimeLength() > 0) {
|
|
||||||
datetime = new byte[getDateTimeLength()];
|
|
||||||
|
|
||||||
for (int i = getHeadLength(), j = 0; j < getDateTimeLength(); i++, j++) {
|
|
||||||
datetime[j] = listRawData.get(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getBodyLength() > 0) {
|
|
||||||
body = new byte[getBodyLength()];
|
|
||||||
|
|
||||||
for (int i = (getHeadLength() + getDateTimeLength()), j = 0; j < getBodyLength(); i++, j++) {
|
|
||||||
body[j] = listRawData.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDateTimeString() {
|
|
||||||
return this.DT == null ? "Unknown" : this.DT;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDecodedDataAsString() {
|
|
||||||
if (decodedData == null)
|
|
||||||
if (isNoDataEntry())
|
|
||||||
return "No data";
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
else
|
|
||||||
return decodedData.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasData() {
|
|
||||||
return (decodedData != null) || (isNoDataEntry()) || getEntryTypeName().equals("UnabsorbedInsulin");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isNoDataEntry() {
|
|
||||||
return (sizes[0] == 2 && sizes[1] == 5 && sizes[2] == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, Object> getDecodedData() {
|
|
||||||
return this.decodedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Object getDecodedDataEntry(String key) {
|
|
||||||
return this.decodedData != null ? this.decodedData.get(key) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasDecodedDataEntry(String key) {
|
|
||||||
return this.decodedData.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean showRaw() {
|
|
||||||
return getEntryTypeName().equals("EndResultTotals");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getHeadLength() {
|
|
||||||
return sizes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getDateTimeLength() {
|
|
||||||
return sizes[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getBodyLength() {
|
|
||||||
return sizes[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (this.DT == null) {
|
|
||||||
LOG.error("DT is null. RawData=" + ByteUtil.getHex(this.rawData));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(getToStringStart());
|
|
||||||
sb.append(", DT: " + (this.DT == null ? "null" : StringUtil.getStringInLength(this.DT, 19)));
|
|
||||||
sb.append(", length=");
|
|
||||||
sb.append(getHeadLength());
|
|
||||||
sb.append(",");
|
|
||||||
sb.append(getDateTimeLength());
|
|
||||||
sb.append(",");
|
|
||||||
sb.append(getBodyLength());
|
|
||||||
sb.append("(");
|
|
||||||
sb.append((getHeadLength() + getDateTimeLength() + getBodyLength()));
|
|
||||||
sb.append(")");
|
|
||||||
|
|
||||||
boolean hasData = hasData();
|
|
||||||
|
|
||||||
if (hasData) {
|
|
||||||
sb.append(", data=" + getDecodedDataAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasData && !showRaw()) {
|
|
||||||
sb.append("]");
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (head != null) {
|
|
||||||
sb.append(", head=");
|
|
||||||
sb.append(ByteUtil.shortHexString(this.head));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (datetime != null) {
|
|
||||||
sb.append(", datetime=");
|
|
||||||
sb.append(ByteUtil.shortHexString(this.datetime));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
sb.append(", body=");
|
|
||||||
sb.append(ByteUtil.shortHexString(this.body));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(", rawData=");
|
|
||||||
sb.append(ByteUtil.shortHexString(this.rawData));
|
|
||||||
sb.append("]");
|
|
||||||
|
|
||||||
// sb.append(" DT: ");
|
|
||||||
// sb.append(this.dateTime == null ? " - " : this.dateTime.toString("dd.MM.yyyy HH:mm:ss"));
|
|
||||||
|
|
||||||
// sb.append(" Ext: ");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public abstract int getOpCode();
|
|
||||||
|
|
||||||
|
|
||||||
public abstract String getToStringStart();
|
|
||||||
|
|
||||||
|
|
||||||
public List<Byte> getRawData() {
|
|
||||||
return rawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte getRawDataByIndex(int index) {
|
|
||||||
return rawData.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getUnsignedRawDataByIndex(int index) {
|
|
||||||
return ByteUtil.convertUnsignedByteToInt(rawData.get(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRawData(List<Byte> rawData) {
|
|
||||||
this.rawData = rawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getHead() {
|
|
||||||
return head;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setHead(byte[] head) {
|
|
||||||
this.head = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getDatetime() {
|
|
||||||
return datetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDatetime(byte[] datetime) {
|
|
||||||
this.datetime = datetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getBody() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setBody(byte[] body) {
|
|
||||||
this.body = body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setAtechDateTime(long dt) {
|
|
||||||
this.atechDateTime = dt;
|
|
||||||
this.DT = DateTimeUtil.toString(this.atechDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addDecodedData(String key, Object value) {
|
|
||||||
if (decodedData == null)
|
|
||||||
decodedData = new HashMap<>();
|
|
||||||
|
|
||||||
decodedData.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toShortString() {
|
|
||||||
if (head == null) {
|
|
||||||
return "Unidentified record. ";
|
|
||||||
} else {
|
|
||||||
return "HistoryRecord: head=[" + ByteUtil.shortHexString(this.head) + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsDecodedData(String key) {
|
|
||||||
if (decodedData == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return decodedData.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we extend to CGMS this need to be changed back
|
|
||||||
// public abstract PumpHistoryEntryType getEntryType();
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
abstract class MedtronicHistoryEntry : MedtronicHistoryEntryInterface {
|
||||||
|
|
||||||
|
lateinit var rawData: List<Byte>
|
||||||
|
|
||||||
|
protected var sizes = IntArray(3)
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
lateinit var head: ByteArray
|
||||||
|
lateinit var datetime: ByteArray
|
||||||
|
lateinit var body: ByteArray
|
||||||
|
|
||||||
|
var id: Long = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var DT: String? = null
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var atechDateTime: Long = 0L
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
DT = DateTimeUtil.toString(value)
|
||||||
|
if (isEntryTypeSet() && value != 0L) pumpId = generatePumpId()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var decodedData: MutableMap<String, Any> = mutableMapOf()
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pump id that will be used with AAPS object (time * 1000 + historyType (max is FF = 255)
|
||||||
|
*/
|
||||||
|
open var pumpId: Long = 0L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if history object is already linked to AAPS object (either Treatment, TempBasal or TDD (tdd's
|
||||||
|
* are not actually
|
||||||
|
* linked))
|
||||||
|
*/
|
||||||
|
var linked = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linked object, see linked
|
||||||
|
*/
|
||||||
|
var linkedObject: Any? = null
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
linked = true
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun generatePumpId(): Long
|
||||||
|
|
||||||
|
abstract fun isEntryTypeSet(): Boolean
|
||||||
|
|
||||||
|
override fun setData(listRawData: MutableList<Byte>, doNotProcess: Boolean) {
|
||||||
|
rawData = listRawData
|
||||||
|
|
||||||
|
// System.out.println("Head: " + sizes[0] + ", dates: " + sizes[1] +
|
||||||
|
// ", body=" + sizes[2]);
|
||||||
|
if (!doNotProcess) {
|
||||||
|
head = ByteArray(headLength - 1)
|
||||||
|
for (i in 1 until headLength) {
|
||||||
|
head[i - 1] = listRawData[i]
|
||||||
|
}
|
||||||
|
if (dateTimeLength > 0) {
|
||||||
|
datetime = ByteArray(dateTimeLength)
|
||||||
|
var i = headLength
|
||||||
|
var j = 0
|
||||||
|
while (j < dateTimeLength) {
|
||||||
|
datetime[j] = listRawData[i]
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
datetime = byteArrayOf()
|
||||||
|
|
||||||
|
if (bodyLength > 0) {
|
||||||
|
body = ByteArray(bodyLength)
|
||||||
|
var i = headLength + dateTimeLength
|
||||||
|
var j = 0
|
||||||
|
while (j < bodyLength) {
|
||||||
|
body[j] = listRawData[i]
|
||||||
|
i++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
body = byteArrayOf()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateTimeString: String
|
||||||
|
get() = if (DT == null) "Unknown" else DT!!
|
||||||
|
|
||||||
|
val decodedDataAsString: String
|
||||||
|
get() = if (decodedData.size == 0) if (isNoDataEntry) "No data" else "" else decodedData.toString()
|
||||||
|
|
||||||
|
fun hasData(): Boolean {
|
||||||
|
return decodedData.size == 0 || isNoDataEntry || entryTypeName == "UnabsorbedInsulin"
|
||||||
|
}
|
||||||
|
|
||||||
|
val isNoDataEntry: Boolean
|
||||||
|
get() = sizes[0] == 2 && sizes[1] == 5 && sizes[2] == 0
|
||||||
|
|
||||||
|
fun getDecodedDataEntry(key: String?): Any? {
|
||||||
|
return if (decodedData.containsKey(key)) decodedData[key] else null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasDecodedDataEntry(key: String?): Boolean {
|
||||||
|
return decodedData.containsKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showRaw(): Boolean {
|
||||||
|
return entryTypeName == "EndResultTotals"
|
||||||
|
}
|
||||||
|
|
||||||
|
val headLength: Int
|
||||||
|
get() = sizes[0]
|
||||||
|
|
||||||
|
val dateTimeLength: Int
|
||||||
|
get() = sizes[1]
|
||||||
|
|
||||||
|
val bodyLength: Int
|
||||||
|
get() = sizes[2]
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
// if (DT == null) {
|
||||||
|
// Log.e("", "DT is null. RawData=" + ByteUtil.getHex(rawData))
|
||||||
|
// }
|
||||||
|
sb.append(toStringStart)
|
||||||
|
sb.append(", DT: " + if (DT == null) "null" else StringUtil.getStringInLength(DT, 19))
|
||||||
|
sb.append(", length=")
|
||||||
|
sb.append(headLength)
|
||||||
|
sb.append(",")
|
||||||
|
sb.append(dateTimeLength)
|
||||||
|
sb.append(",")
|
||||||
|
sb.append(bodyLength)
|
||||||
|
sb.append("(")
|
||||||
|
sb.append(headLength + dateTimeLength + bodyLength)
|
||||||
|
sb.append(")")
|
||||||
|
val hasData = hasData()
|
||||||
|
if (hasData) {
|
||||||
|
sb.append(", data=$decodedDataAsString")
|
||||||
|
}
|
||||||
|
if (hasData && !showRaw()) {
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
if (head.size != 0) {
|
||||||
|
sb.append(", head=")
|
||||||
|
sb.append(ByteUtil.shortHexString(head))
|
||||||
|
}
|
||||||
|
if (datetime.size != 0) {
|
||||||
|
sb.append(", datetime=")
|
||||||
|
sb.append(ByteUtil.shortHexString(datetime))
|
||||||
|
}
|
||||||
|
if (body.size != 0) {
|
||||||
|
sb.append(", body=")
|
||||||
|
sb.append(ByteUtil.shortHexString(body))
|
||||||
|
}
|
||||||
|
sb.append(", rawData=")
|
||||||
|
sb.append(ByteUtil.shortHexString(rawData))
|
||||||
|
sb.append("]")
|
||||||
|
|
||||||
|
// sb.append(" DT: ");
|
||||||
|
// sb.append(this.dateTime == null ? " - " : this.dateTime.toString("dd.MM.yyyy HH:mm:ss"));
|
||||||
|
|
||||||
|
// sb.append(" Ext: ");
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract val opCode: Byte?
|
||||||
|
abstract val toStringStart: String?
|
||||||
|
|
||||||
|
fun getRawDataByIndex(index: Int): Byte {
|
||||||
|
return rawData[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRawDataByIndexInt(index: Int): Int {
|
||||||
|
return rawData[index].toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUnsignedRawDataByIndex(index: Int): Int {
|
||||||
|
return ByteUtil.convertUnsignedByteToInt(rawData[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDecodedData(key: String, value: Any) {
|
||||||
|
decodedData.put(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toShortString(): String {
|
||||||
|
return if (head.size != 0) {
|
||||||
|
"Unidentified record. "
|
||||||
|
} else {
|
||||||
|
"HistoryRecord: head=[" + ByteUtil.shortHexString(head) + "]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsDecodedData(key: String?): Boolean {
|
||||||
|
return decodedData.containsKey(key)
|
||||||
|
} // if we extend to CGMS this need to be changed back
|
||||||
|
// public abstract PumpHistoryEntryType getEntryType();
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 7/24/18.
|
|
||||||
*/
|
|
||||||
public interface MedtronicHistoryEntryInterface {
|
|
||||||
|
|
||||||
String getEntryTypeName();
|
|
||||||
|
|
||||||
void setData(List<Byte> listRawData, boolean doNotProcess);
|
|
||||||
|
|
||||||
int getDateLength();
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 7/24/18.
|
||||||
|
*/
|
||||||
|
interface MedtronicHistoryEntryInterface {
|
||||||
|
|
||||||
|
val entryTypeName: String
|
||||||
|
fun setData(listRawData: MutableList<Byte>, doNotProcess: Boolean)
|
||||||
|
val dateLength: Int
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.CRC;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 6/4/16.
|
|
||||||
*/
|
|
||||||
public class RawHistoryPage {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
|
|
||||||
private byte[] data = new byte[0];
|
|
||||||
|
|
||||||
|
|
||||||
public RawHistoryPage(AAPSLogger aapsLogger) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void appendData(byte[] newdata) {
|
|
||||||
data = ByteUtil.concat(data, newdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
byte[] getOnlyData() {
|
|
||||||
return Arrays.copyOfRange(data, 0, 1022);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getLength() {
|
|
||||||
return data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isChecksumOK() {
|
|
||||||
if (getLength() != 1024) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
byte[] computedCRC = CRC.calculate16CCITT(ByteUtil.substring(data, 0, 1022));
|
|
||||||
|
|
||||||
int crcCalculated = ByteUtil.toInt(computedCRC[0], computedCRC[1]);
|
|
||||||
int crcStored = ByteUtil.toInt(data[1022], data[1023]);
|
|
||||||
|
|
||||||
if (crcCalculated != crcStored) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Stored CRC (%d) is different than calculated (%d), but ignored for now.", crcStored,
|
|
||||||
crcCalculated));
|
|
||||||
} else {
|
|
||||||
if (MedtronicUtil.isLowLevelDebug())
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "CRC ok.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return crcCalculated == crcStored;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void dumpToDebug() {
|
|
||||||
int linesize = 80;
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
while (offset < data.length) {
|
|
||||||
int bytesToLog = linesize;
|
|
||||||
if (offset + linesize > data.length) {
|
|
||||||
bytesToLog = data.length - offset;
|
|
||||||
}
|
|
||||||
sb.append(ByteUtil.shortHexString(ByteUtil.substring(data, offset, bytesToLog)) + " ");
|
|
||||||
// sb.append("\n");
|
|
||||||
|
|
||||||
offset += linesize;
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMPBTCOMM, "History Page Data:\n" + sb.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.CRC
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/4/16.
|
||||||
|
*/
|
||||||
|
class RawHistoryPage(private val aapsLogger: AAPSLogger) {
|
||||||
|
|
||||||
|
var data = ByteArray(0)
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun appendData(newdata: ByteArray?) {
|
||||||
|
data = ByteUtil.concat(data, newdata)
|
||||||
|
}
|
||||||
|
|
||||||
|
val onlyData: ByteArray
|
||||||
|
get() = Arrays.copyOfRange(data, 0, 1022)
|
||||||
|
|
||||||
|
val length: Int
|
||||||
|
get() = data.size
|
||||||
|
|
||||||
|
val isChecksumOK: Boolean
|
||||||
|
get() {
|
||||||
|
if (length != 1024) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val computedCRC = CRC.calculate16CCITT(ByteUtil.substring(data, 0, 1022))
|
||||||
|
val crcCalculated = ByteUtil.toInt(computedCRC[0].toInt(), computedCRC[1].toInt())
|
||||||
|
val crcStored = ByteUtil.toInt(data[1022].toInt(), data[1023].toInt())
|
||||||
|
if (crcCalculated != crcStored) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Stored CRC (%d) is different than calculated (%d), but ignored for now.", crcStored,
|
||||||
|
crcCalculated))
|
||||||
|
} else {
|
||||||
|
if (MedtronicUtil.isLowLevelDebug) aapsLogger.debug(LTag.PUMPBTCOMM, "CRC ok.")
|
||||||
|
}
|
||||||
|
return crcCalculated == crcStored
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dumpToDebug() {
|
||||||
|
val linesize = 80
|
||||||
|
var offset = 0
|
||||||
|
val sb = StringBuilder()
|
||||||
|
while (offset < data.size) {
|
||||||
|
var bytesToLog = linesize
|
||||||
|
if (offset + linesize > data.size) {
|
||||||
|
bytesToLog = data.size - offset
|
||||||
|
}
|
||||||
|
sb.append(ByteUtil.shortHexString(ByteUtil.substring(data, offset, bytesToLog)) + " ")
|
||||||
|
// sb.append("\n");
|
||||||
|
offset += linesize
|
||||||
|
}
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "History Page Data:\n$sb")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
*
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum RecordDecodeStatus {
|
|
||||||
OK("OK "), //
|
|
||||||
Ignored("IGNORE "), //
|
|
||||||
NotSupported("N/A YET"), //
|
|
||||||
Error("ERROR "), //
|
|
||||||
WIP("WIP "), //
|
|
||||||
Unknown("UNK ");
|
|
||||||
|
|
||||||
String description;
|
|
||||||
|
|
||||||
|
|
||||||
RecordDecodeStatus(String description) {
|
|
||||||
this.description = description;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
enum class RecordDecodeStatus(var description: String) {
|
||||||
|
|
||||||
|
OK("OK "), //
|
||||||
|
Ignored("IGNORE "), //
|
||||||
|
NotSupported("N/A YET"), //
|
||||||
|
Error("ERROR "), //
|
||||||
|
WIP("WIP "), //
|
||||||
|
Unknown("UNK ");
|
||||||
|
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
*
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class CGMSHistoryEntry extends MedtronicHistoryEntry {
|
|
||||||
|
|
||||||
private CGMSHistoryEntryType entryType;
|
|
||||||
private Integer opCode; // this is set only when we have unknown entry...
|
|
||||||
|
|
||||||
|
|
||||||
public CGMSHistoryEntryType getEntryType() {
|
|
||||||
return entryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setEntryType(CGMSHistoryEntryType entryType) {
|
|
||||||
this.entryType = entryType;
|
|
||||||
|
|
||||||
this.sizes[0] = entryType.getHeadLength();
|
|
||||||
this.sizes[1] = entryType.getDateLength();
|
|
||||||
this.sizes[2] = entryType.getBodyLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEntryTypeName() {
|
|
||||||
return this.entryType.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setData(List<Byte> listRawData, boolean doNotProcess) {
|
|
||||||
if (this.entryType.schemaSet) {
|
|
||||||
super.setData(listRawData, doNotProcess);
|
|
||||||
} else {
|
|
||||||
this.rawData = listRawData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDateLength() {
|
|
||||||
return this.entryType.getDateLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpCode() {
|
|
||||||
if (opCode == null)
|
|
||||||
return entryType.getOpCode();
|
|
||||||
else
|
|
||||||
return opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setOpCode(Integer opCode) {
|
|
||||||
this.opCode = opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasTimeStamp() {
|
|
||||||
return (this.entryType.hasDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToStringStart() {
|
|
||||||
|
|
||||||
return "CGMSHistoryEntry [type=" + StringUtils.rightPad(entryType.name(), 18) + " ["
|
|
||||||
+ StringUtils.leftPad("" + getOpCode(), 3) + ", 0x" + ByteUtil.getCorrectHexValue(getOpCode()) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDateTime(LocalDateTime timeStamp, int getIndex) {
|
|
||||||
|
|
||||||
setAtechDateTime(DateTimeUtil.toATechDate(timeStamp.plusMinutes(getIndex * 5)));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
class CGMSHistoryEntry : MedtronicHistoryEntry() {
|
||||||
|
|
||||||
|
var entryType: CGMSHistoryEntryType = CGMSHistoryEntryType.UnknownOpCode
|
||||||
|
private set
|
||||||
|
|
||||||
|
override var opCode: Byte? = null // this is set only when we have unknown entry...
|
||||||
|
get() = if (field == null) entryType.code.toByte() else field
|
||||||
|
|
||||||
|
fun setEntryType(entryType: CGMSHistoryEntryType) {
|
||||||
|
this.entryType = entryType
|
||||||
|
sizes[0] = entryType.headLength
|
||||||
|
sizes[1] = entryType.dateLength
|
||||||
|
sizes[2] = entryType.bodyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
override val entryTypeName: String
|
||||||
|
get() = entryType.name
|
||||||
|
|
||||||
|
override fun generatePumpId(): Long {
|
||||||
|
return entryType.code + atechDateTime * 1000L
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEntryTypeSet(): Boolean {
|
||||||
|
return entryType != CGMSHistoryEntryType.UnknownOpCode
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setData(listRawData: MutableList<Byte>, doNotProcess: Boolean) {
|
||||||
|
if (entryType.schemaSet) {
|
||||||
|
super.setData(listRawData, doNotProcess)
|
||||||
|
} else {
|
||||||
|
rawData = listRawData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val dateLength: Int
|
||||||
|
get() = entryType.dateLength
|
||||||
|
|
||||||
|
fun hasTimeStamp(): Boolean {
|
||||||
|
return entryType.hasDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val toStringStart: String
|
||||||
|
get() = ("CGMSHistoryEntry [type=" + StringUtils.rightPad(entryType.name, 18) + " ["
|
||||||
|
+ StringUtils.leftPad("" + opCode, 3) + ", 0x" + ByteUtil.getCorrectHexValue(opCode!!) + "]")
|
||||||
|
|
||||||
|
fun setDateTime(timeStamp: LocalDateTime, getIndex: Int) {
|
||||||
|
atechDateTime = (DateTimeUtil.toATechDate(timeStamp.plusMinutes(getIndex * 5)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,135 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
*
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum CGMSHistoryEntryType {
|
|
||||||
|
|
||||||
None(0, "None", 1, 0, 0, DateType.None), //
|
|
||||||
|
|
||||||
DataEnd(0x01, "DataEnd", 1, 0, 0, DateType.PreviousTimeStamp), //
|
|
||||||
SensorWeakSignal(0x02, "SensorWeakSignal", 1, 0, 0, DateType.PreviousTimeStamp), //
|
|
||||||
SensorCal(0x03, "SensorCal", 1, 0, 1, DateType.PreviousTimeStamp), //
|
|
||||||
SensorPacket(0x04, "SensorPacket", 1, 0, 1, DateType.PreviousTimeStamp),
|
|
||||||
SensorError(0x05, "SensorError", 1, 0, 1, DateType.PreviousTimeStamp),
|
|
||||||
SensorDataLow(0x06, "SensorDataLow", 1, 0, 1, DateType.PreviousTimeStamp),
|
|
||||||
SensorDataHigh(0x07, "SensorDataHigh", 1, 0, 1, DateType.PreviousTimeStamp),
|
|
||||||
SensorTimestamp(0x08, "SensorTimestamp", 1, 4, 0, DateType.MinuteSpecific), //
|
|
||||||
BatteryChange(0x0a, "BatteryChange", 1, 4, 0, DateType.MinuteSpecific), //
|
|
||||||
SensorStatus(0x0b, "SensorStatus", 1, 4, 0, DateType.MinuteSpecific), //
|
|
||||||
DateTimeChange(0x0c, "DateTimeChange", 1, 4, 0, DateType.SecondSpecific), //
|
|
||||||
SensorSync(0x0d, "SensorSync',packet_size=4", 1, 4, 0, DateType.MinuteSpecific), //
|
|
||||||
CalBGForGH(0x0e, "CalBGForGH',packet_size=5", 1, 4, 1, DateType.MinuteSpecific), //
|
|
||||||
SensorCalFactor(0x0f, "SensorCalFactor", 1, 4, 2, DateType.MinuteSpecific), //
|
|
||||||
Something10(0x10, "10-Something", 1, 4, 0, DateType.MinuteSpecific), //
|
|
||||||
Something19(0x13, "19-Something", 1, 0, 0, DateType.PreviousTimeStamp),
|
|
||||||
GlucoseSensorData(0xFF, "GlucoseSensorData", 1, 0, 0, DateType.PreviousTimeStamp);
|
|
||||||
|
|
||||||
private static final Map<Integer, CGMSHistoryEntryType> opCodeMap = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (CGMSHistoryEntryType type : values()) {
|
|
||||||
opCodeMap.put(type.opCode, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean schemaSet;
|
|
||||||
private final int opCode;
|
|
||||||
private final String description;
|
|
||||||
private final int headLength;
|
|
||||||
private final int dateLength;
|
|
||||||
private final int bodyLength;
|
|
||||||
private final int totalLength;
|
|
||||||
private final DateType dateType;
|
|
||||||
|
|
||||||
|
|
||||||
CGMSHistoryEntryType(int opCode, String name, int head, int date, int body, DateType dateType) {
|
|
||||||
this.opCode = opCode;
|
|
||||||
this.description = name;
|
|
||||||
this.headLength = head;
|
|
||||||
this.dateLength = date;
|
|
||||||
this.bodyLength = body;
|
|
||||||
this.totalLength = (head + date + body);
|
|
||||||
this.schemaSet = true;
|
|
||||||
this.dateType = dateType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// private CGMSHistoryEntryType(int opCode, String name, int length)
|
|
||||||
// {
|
|
||||||
// this.opCode = opCode;
|
|
||||||
// this.description = name;
|
|
||||||
// this.headLength = 0;
|
|
||||||
// this.dateLength = 0;
|
|
||||||
// this.bodyLength = 0;
|
|
||||||
// this.totalLength = length + 1; // opCode
|
|
||||||
// }
|
|
||||||
|
|
||||||
public static CGMSHistoryEntryType getByCode(int opCode) {
|
|
||||||
if (opCodeMap.containsKey(opCode)) {
|
|
||||||
return opCodeMap.get(opCode);
|
|
||||||
} else
|
|
||||||
return CGMSHistoryEntryType.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getCode() {
|
|
||||||
return this.opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getTotalLength() {
|
|
||||||
return totalLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getOpCode() {
|
|
||||||
return opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getHeadLength() {
|
|
||||||
return headLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getDateLength() {
|
|
||||||
return dateLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getBodyLength() {
|
|
||||||
return bodyLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public DateType getDateType() {
|
|
||||||
return dateType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasDate() {
|
|
||||||
return (this.dateType == DateType.MinuteSpecific) || (this.dateType == DateType.SecondSpecific);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DateType {
|
|
||||||
None, //
|
|
||||||
MinuteSpecific, //
|
|
||||||
SecondSpecific, //
|
|
||||||
PreviousTimeStamp //
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
enum class CGMSHistoryEntryType(val code: Int, val description: String, val headLength: Int, val dateLength: Int, val bodyLength: Int, dateType: DateType) {
|
||||||
|
|
||||||
|
None(0, "None", 1, 0, 0, DateType.None), //
|
||||||
|
DataEnd(0x01, "DataEnd", 1, 0, 0, DateType.PreviousTimeStamp), //
|
||||||
|
SensorWeakSignal(0x02, "SensorWeakSignal", 1, 0, 0, DateType.PreviousTimeStamp), //
|
||||||
|
SensorCal(0x03, "SensorCal", 1, 0, 1, DateType.PreviousTimeStamp), //
|
||||||
|
SensorPacket(0x04, "SensorPacket", 1, 0, 1, DateType.PreviousTimeStamp), SensorError(0x05, "SensorError", 1, 0, 1, DateType.PreviousTimeStamp), SensorDataLow(0x06, "SensorDataLow", 1, 0, 1, DateType.PreviousTimeStamp), SensorDataHigh(0x07, "SensorDataHigh", 1, 0, 1, DateType.PreviousTimeStamp), SensorTimestamp(0x08, "SensorTimestamp", 1, 4, 0, DateType.MinuteSpecific), //
|
||||||
|
BatteryChange(0x0a, "BatteryChange", 1, 4, 0, DateType.MinuteSpecific), //
|
||||||
|
SensorStatus(0x0b, "SensorStatus", 1, 4, 0, DateType.MinuteSpecific), //
|
||||||
|
DateTimeChange(0x0c, "DateTimeChange", 1, 4, 0, DateType.SecondSpecific), //
|
||||||
|
SensorSync(0x0d, "SensorSync',packet_size=4", 1, 4, 0, DateType.MinuteSpecific), //
|
||||||
|
CalBGForGH(0x0e, "CalBGForGH',packet_size=5", 1, 4, 1, DateType.MinuteSpecific), //
|
||||||
|
SensorCalFactor(0x0f, "SensorCalFactor", 1, 4, 2, DateType.MinuteSpecific), //
|
||||||
|
Something10(0x10, "10-Something", 1, 4, 0, DateType.MinuteSpecific), //
|
||||||
|
Something19(0x13, "19-Something", 1, 0, 0, DateType.PreviousTimeStamp), GlucoseSensorData(0xFF, "GlucoseSensorData", 1, 0, 0, DateType.PreviousTimeStamp),
|
||||||
|
UnknownOpCode(0xFF, "Unknown", 0, 0, 0, DateType.None);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val opCodeMap: MutableMap<Int, CGMSHistoryEntryType> = mutableMapOf()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getByCode(opCode: Int): CGMSHistoryEntryType {
|
||||||
|
return if (opCodeMap.containsKey(opCode))
|
||||||
|
opCodeMap[opCode]!!
|
||||||
|
else
|
||||||
|
UnknownOpCode
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (type in values()) {
|
||||||
|
opCodeMap[type.code] = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmField var schemaSet: Boolean
|
||||||
|
val totalLength: Int
|
||||||
|
val dateType: DateType
|
||||||
|
|
||||||
|
fun hasDate(): Boolean {
|
||||||
|
return dateType == DateType.MinuteSpecific || dateType == DateType.SecondSpecific
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DateType {
|
||||||
|
None, //
|
||||||
|
MinuteSpecific, //
|
||||||
|
SecondSpecific, //
|
||||||
|
PreviousTimeStamp //
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
totalLength = headLength + dateLength + bodyLength
|
||||||
|
schemaSet = true
|
||||||
|
this.dateType = dateType
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,469 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms;
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MedtronicCGMSHistoryDecoder extends MedtronicHistoryDecoder<CGMSHistoryEntry> {
|
|
||||||
|
|
||||||
//private static final Logger LOG = StacktraceLoggerWrapper.getLogger(LTag.PUMPCOMM);
|
|
||||||
|
|
||||||
|
|
||||||
// CGMSValuesWriter cgmsValuesWriter = null;
|
|
||||||
|
|
||||||
public MedtronicCGMSHistoryDecoder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public RecordDecodeStatus decodeRecord(CGMSHistoryEntry record) {
|
|
||||||
try {
|
|
||||||
return decodeRecord(record, false);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
//LOG.error(" Error decoding: type={}, ex={}", record.getEntryType().name(), ex.getMessage(), ex);
|
|
||||||
return RecordDecodeStatus.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public RecordDecodeStatus decodeRecord(CGMSHistoryEntry entry, boolean x) {
|
|
||||||
|
|
||||||
if (entry.getDateTimeLength() > 0) {
|
|
||||||
parseDate(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (entry.getEntryType()) {
|
|
||||||
|
|
||||||
case SensorPacket:
|
|
||||||
decodeSensorPacket(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorError:
|
|
||||||
decodeSensorError(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorDataLow:
|
|
||||||
decodeDataHighLow(entry, 40);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorDataHigh:
|
|
||||||
decodeDataHighLow(entry, 400);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorTimestamp:
|
|
||||||
decodeSensorTimestamp(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorCal:
|
|
||||||
decodeSensorCal(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorCalFactor:
|
|
||||||
decodeSensorCalFactor(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorSync:
|
|
||||||
decodeSensorSync(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SensorStatus:
|
|
||||||
decodeSensorStatus(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CalBGForGH:
|
|
||||||
decodeCalBGForGH(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GlucoseSensorData:
|
|
||||||
decodeGlucoseSensorData(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// just timestamp
|
|
||||||
case BatteryChange:
|
|
||||||
case Something10:
|
|
||||||
case DateTimeChange:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// just relative timestamp
|
|
||||||
case Something19:
|
|
||||||
case DataEnd:
|
|
||||||
case SensorWeakSignal:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case None:
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return RecordDecodeStatus.NotSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postProcess() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<CGMSHistoryEntry> createRecords(List<Byte> dataClearInput) {
|
|
||||||
|
|
||||||
List<Byte> dataClear = reverseList(dataClearInput, Byte.class);
|
|
||||||
|
|
||||||
prepareStatistics();
|
|
||||||
|
|
||||||
int counter = 0;
|
|
||||||
|
|
||||||
List<CGMSHistoryEntry> outList = new ArrayList<CGMSHistoryEntry>();
|
|
||||||
|
|
||||||
// create CGMS entries (without dates)
|
|
||||||
do {
|
|
||||||
int opCode = getUnsignedInt(dataClear.get(counter));
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
CGMSHistoryEntryType entryType;
|
|
||||||
|
|
||||||
if (opCode == 0) {
|
|
||||||
// continue;
|
|
||||||
} else if ((opCode > 0) && (opCode < 20)) {
|
|
||||||
entryType = CGMSHistoryEntryType.getByCode(opCode);
|
|
||||||
|
|
||||||
if (entryType == CGMSHistoryEntryType.None) {
|
|
||||||
this.unknownOpCodes.put(opCode, opCode);
|
|
||||||
//LOG.warn("GlucoseHistoryEntry with unknown code: " + opCode);
|
|
||||||
|
|
||||||
CGMSHistoryEntry pe = new CGMSHistoryEntry();
|
|
||||||
pe.setEntryType(CGMSHistoryEntryType.None);
|
|
||||||
pe.setOpCode(opCode);
|
|
||||||
|
|
||||||
pe.setData(Arrays.asList((byte) opCode), false);
|
|
||||||
|
|
||||||
outList.add(pe);
|
|
||||||
} else {
|
|
||||||
// System.out.println("OpCode: " + opCode);
|
|
||||||
|
|
||||||
List<Byte> listRawData = new ArrayList<Byte>();
|
|
||||||
listRawData.add((byte) opCode);
|
|
||||||
|
|
||||||
for (int j = 0; j < (entryType.getTotalLength() - 1); j++) {
|
|
||||||
listRawData.add(dataClear.get(counter));
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGMSHistoryEntry pe = new CGMSHistoryEntry();
|
|
||||||
pe.setEntryType(entryType);
|
|
||||||
|
|
||||||
pe.setOpCode(opCode);
|
|
||||||
pe.setData(listRawData, false);
|
|
||||||
|
|
||||||
// System.out.println("Record: " + pe);
|
|
||||||
|
|
||||||
outList.add(pe);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CGMSHistoryEntry pe = new CGMSHistoryEntry();
|
|
||||||
pe.setEntryType(CGMSHistoryEntryType.GlucoseSensorData);
|
|
||||||
|
|
||||||
pe.setData(Arrays.asList((byte) opCode), false);
|
|
||||||
|
|
||||||
outList.add(pe);
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (counter < dataClear.size());
|
|
||||||
|
|
||||||
List<CGMSHistoryEntry> reversedOutList = reverseList(outList, CGMSHistoryEntry.class);
|
|
||||||
|
|
||||||
Long timeStamp = null;
|
|
||||||
LocalDateTime dateTime = null;
|
|
||||||
int getIndex = 0;
|
|
||||||
|
|
||||||
for (CGMSHistoryEntry entry : reversedOutList) {
|
|
||||||
|
|
||||||
decodeRecord(entry);
|
|
||||||
|
|
||||||
if (entry.hasTimeStamp()) {
|
|
||||||
timeStamp = entry.atechDateTime;
|
|
||||||
dateTime = DateTimeUtil.toLocalDateTime(timeStamp);
|
|
||||||
getIndex = 0;
|
|
||||||
} else if (entry.getEntryType() == CGMSHistoryEntryType.GlucoseSensorData) {
|
|
||||||
getIndex++;
|
|
||||||
if (dateTime != null)
|
|
||||||
entry.setDateTime(dateTime, getIndex);
|
|
||||||
} else {
|
|
||||||
if (dateTime != null)
|
|
||||||
entry.setDateTime(dateTime, getIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
//LOG.debug("Record: {}", entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return reversedOutList;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private <E> List<E> reverseList(List<E> dataClearInput, Class<E> clazz) {
|
|
||||||
|
|
||||||
List<E> outList = new ArrayList<E>();
|
|
||||||
|
|
||||||
for (int i = dataClearInput.size() - 1; i > 0; i--) {
|
|
||||||
outList.add(dataClearInput.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return outList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int parseMinutes(int one) {
|
|
||||||
return (one & Integer.parseInt("0111111", 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int parseHours(int one) {
|
|
||||||
return (one & 0x1F);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int parseDay(int one) {
|
|
||||||
return one & 0x1F;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int parseMonths(int first_byte, int second_byte) {
|
|
||||||
|
|
||||||
int first_two_bits = first_byte >> 6;
|
|
||||||
int second_two_bits = second_byte >> 6;
|
|
||||||
|
|
||||||
return (first_two_bits << 2) + second_two_bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int parseYear(int year) {
|
|
||||||
return (year & 0x0F) + 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Long parseDate(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
if (!entry.getEntryType().hasDate())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] data = entry.getDatetime();
|
|
||||||
|
|
||||||
if (entry.getEntryType().getDateType() == CGMSHistoryEntryType.DateType.MinuteSpecific) {
|
|
||||||
|
|
||||||
Long atechDateTime = DateTimeUtil.toATechDate(parseYear(data[3]), parseMonths(data[0], data[1]),
|
|
||||||
parseDay(data[2]), parseHours(data[0]), parseMinutes(data[1]), 0);
|
|
||||||
|
|
||||||
entry.setAtechDateTime(atechDateTime);
|
|
||||||
|
|
||||||
return atechDateTime;
|
|
||||||
|
|
||||||
} else if (entry.getEntryType().getDateType() == CGMSHistoryEntryType.DateType.SecondSpecific) {
|
|
||||||
//LOG.warn("parseDate for SecondSpecific type is not implemented.");
|
|
||||||
throw new RuntimeException();
|
|
||||||
// return null;
|
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeGlucoseSensorData(CGMSHistoryEntry entry) {
|
|
||||||
int sgv = entry.getUnsignedRawDataByIndex(0) * 2;
|
|
||||||
entry.addDecodedData("sgv", sgv);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeCalBGForGH(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
int amount = ((entry.getRawDataByIndex(3) & 0b00100000) << 3) | entry.getRawDataByIndex(5);
|
|
||||||
//
|
|
||||||
String originType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) {
|
|
||||||
case 0x00:
|
|
||||||
originType = "rf";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
originType = "unknown";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("amount", amount);
|
|
||||||
entry.addDecodedData("originType", originType);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorSync(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String syncType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) {
|
|
||||||
case 0x01:
|
|
||||||
syncType = "new";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x02:
|
|
||||||
syncType = "old";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
syncType = "find";
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("syncType", syncType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorStatus(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String statusType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) {
|
|
||||||
case 0x00:
|
|
||||||
statusType = "off";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x01:
|
|
||||||
statusType = "on";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x02:
|
|
||||||
statusType = "lost";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
statusType = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("statusType", statusType);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorCalFactor(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
double factor = (entry.getRawDataByIndex(5) << 8 | entry.getRawDataByIndex(6)) / 1000.0d;
|
|
||||||
|
|
||||||
entry.addDecodedData("factor", factor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorCal(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String calibrationType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(1)) {
|
|
||||||
case 0x00:
|
|
||||||
calibrationType = "meter_bg_now";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x01:
|
|
||||||
calibrationType = "waiting";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x02:
|
|
||||||
calibrationType = "cal_error";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
calibrationType = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("calibrationType", calibrationType);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorTimestamp(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String sensorTimestampType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) {
|
|
||||||
|
|
||||||
case 0x00:
|
|
||||||
sensorTimestampType = "LastRf";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x01:
|
|
||||||
sensorTimestampType = "PageEnd";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0x02:
|
|
||||||
sensorTimestampType = "Gap";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
sensorTimestampType = "Unknown";
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("sensorTimestampType", sensorTimestampType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorPacket(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String packetType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(1)) {
|
|
||||||
case 0x02:
|
|
||||||
packetType = "init";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
packetType = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("packetType", packetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeSensorError(CGMSHistoryEntry entry) {
|
|
||||||
|
|
||||||
String errorType;
|
|
||||||
|
|
||||||
switch (entry.getRawDataByIndex(1)) {
|
|
||||||
case 0x01:
|
|
||||||
errorType = "end";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
errorType = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.addDecodedData("errorType", errorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeDataHighLow(CGMSHistoryEntry entry, int sgv) {
|
|
||||||
entry.addDecodedData("sgv", sgv);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runPostDecodeTasks() {
|
|
||||||
this.showStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms.CGMSHistoryEntryType.Companion.getByCode
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import okhttp3.internal.and
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
class MedtronicCGMSHistoryDecoder constructor(
|
||||||
|
aapsLogger: AAPSLogger,
|
||||||
|
medtronicUtil: MedtronicUtil,
|
||||||
|
bitUtils: ByteUtil
|
||||||
|
) : MedtronicHistoryDecoder<CGMSHistoryEntry>(aapsLogger, medtronicUtil, bitUtils) {
|
||||||
|
|
||||||
|
override fun decodeRecord(record: CGMSHistoryEntry): RecordDecodeStatus? {
|
||||||
|
return try {
|
||||||
|
decodeRecordInternal(record)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, " Error decoding: type={}, ex={}", record.entryType.name, ex.message, ex)
|
||||||
|
RecordDecodeStatus.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeRecordInternal(entry: CGMSHistoryEntry): RecordDecodeStatus {
|
||||||
|
if (entry.dateTimeLength > 0) {
|
||||||
|
parseDate(entry)
|
||||||
|
}
|
||||||
|
when (entry.entryType) {
|
||||||
|
CGMSHistoryEntryType.SensorPacket -> decodeSensorPacket(entry)
|
||||||
|
CGMSHistoryEntryType.SensorError -> decodeSensorError(entry)
|
||||||
|
CGMSHistoryEntryType.SensorDataLow -> decodeDataHighLow(entry, 40)
|
||||||
|
CGMSHistoryEntryType.SensorDataHigh -> decodeDataHighLow(entry, 400)
|
||||||
|
CGMSHistoryEntryType.SensorTimestamp -> decodeSensorTimestamp(entry)
|
||||||
|
CGMSHistoryEntryType.SensorCal -> decodeSensorCal(entry)
|
||||||
|
CGMSHistoryEntryType.SensorCalFactor -> decodeSensorCalFactor(entry)
|
||||||
|
CGMSHistoryEntryType.SensorSync -> decodeSensorSync(entry)
|
||||||
|
CGMSHistoryEntryType.SensorStatus -> decodeSensorStatus(entry)
|
||||||
|
CGMSHistoryEntryType.CalBGForGH -> decodeCalBGForGH(entry)
|
||||||
|
CGMSHistoryEntryType.GlucoseSensorData -> decodeGlucoseSensorData(entry)
|
||||||
|
|
||||||
|
CGMSHistoryEntryType.BatteryChange,
|
||||||
|
CGMSHistoryEntryType.Something10,
|
||||||
|
CGMSHistoryEntryType.DateTimeChange -> {
|
||||||
|
}
|
||||||
|
|
||||||
|
CGMSHistoryEntryType.Something19,
|
||||||
|
CGMSHistoryEntryType.DataEnd,
|
||||||
|
CGMSHistoryEntryType.SensorWeakSignal -> {
|
||||||
|
}
|
||||||
|
|
||||||
|
CGMSHistoryEntryType.UnknownOpCode,
|
||||||
|
CGMSHistoryEntryType.None -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RecordDecodeStatus.NotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun postProcess() {}
|
||||||
|
|
||||||
|
override fun createRecords(dataClearInput: MutableList<Byte>): MutableList<CGMSHistoryEntry> {
|
||||||
|
dataClearInput.reverse()
|
||||||
|
val dataClear = dataClearInput //reverseList(dataClearInput, Byte::class.java)
|
||||||
|
prepareStatistics()
|
||||||
|
var counter = 0
|
||||||
|
val outList: MutableList<CGMSHistoryEntry> = mutableListOf()
|
||||||
|
|
||||||
|
// create CGMS entries (without dates)
|
||||||
|
do {
|
||||||
|
val opCode = getUnsignedInt(dataClear[counter])
|
||||||
|
counter++
|
||||||
|
var entryType: CGMSHistoryEntryType?
|
||||||
|
if (opCode == 0) {
|
||||||
|
// continue;
|
||||||
|
} else if (opCode > 0 && opCode < 20) {
|
||||||
|
entryType = getByCode(opCode)
|
||||||
|
if (entryType === CGMSHistoryEntryType.None) {
|
||||||
|
unknownOpCodes[opCode] = opCode
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "GlucoseHistoryEntry with unknown code: $opCode")
|
||||||
|
val pe = CGMSHistoryEntry()
|
||||||
|
pe.setEntryType(CGMSHistoryEntryType.None)
|
||||||
|
pe.opCode = opCode.toByte()
|
||||||
|
pe.setData(Arrays.asList(opCode.toByte()), false)
|
||||||
|
outList.add(pe)
|
||||||
|
} else {
|
||||||
|
// System.out.println("OpCode: " + opCode);
|
||||||
|
val listRawData: MutableList<Byte> = ArrayList()
|
||||||
|
listRawData.add(opCode.toByte())
|
||||||
|
for (j in 0 until entryType.totalLength - 1) {
|
||||||
|
listRawData.add(dataClear[counter])
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
val pe = CGMSHistoryEntry()
|
||||||
|
pe.setEntryType(entryType)
|
||||||
|
pe.opCode = opCode.toByte()
|
||||||
|
pe.setData(listRawData, false)
|
||||||
|
|
||||||
|
// System.out.println("Record: " + pe);
|
||||||
|
outList.add(pe)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val pe = CGMSHistoryEntry()
|
||||||
|
pe.setEntryType(CGMSHistoryEntryType.GlucoseSensorData)
|
||||||
|
pe.setData(Arrays.asList(opCode.toByte()), false)
|
||||||
|
outList.add(pe)
|
||||||
|
}
|
||||||
|
} while (counter < dataClear.size)
|
||||||
|
outList.reverse()
|
||||||
|
val reversedOutList = outList // reverseList(outList, CGMSHistoryEntry::class.java)
|
||||||
|
//var timeStamp: Long? = null
|
||||||
|
var dateTime: LocalDateTime? = null
|
||||||
|
var getIndex = 0
|
||||||
|
for (entry in reversedOutList) {
|
||||||
|
decodeRecord(entry)
|
||||||
|
if (entry.hasTimeStamp()) {
|
||||||
|
//timeStamp = entry.atechDateTime
|
||||||
|
dateTime = DateTimeUtil.toLocalDateTime(entry.atechDateTime)
|
||||||
|
getIndex = 0
|
||||||
|
} else if (entry.entryType == CGMSHistoryEntryType.GlucoseSensorData) {
|
||||||
|
getIndex++
|
||||||
|
if (dateTime != null) entry.setDateTime(dateTime, getIndex)
|
||||||
|
} else {
|
||||||
|
if (dateTime != null) entry.setDateTime(dateTime, getIndex)
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Record: {}", entry)
|
||||||
|
}
|
||||||
|
return reversedOutList
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun <E> reverseList(dataClearInput: List<E>, clazz: Class<E>): List<E> {
|
||||||
|
// val outList: MutableList<E> = ArrayList()
|
||||||
|
// for (i in dataClearInput.size - 1 downTo 1) {
|
||||||
|
// outList.add(dataClearInput[i])
|
||||||
|
// }
|
||||||
|
// return outList
|
||||||
|
// }
|
||||||
|
|
||||||
|
private fun parseMinutes(one: Int): Int {
|
||||||
|
return one and "0111111".toInt(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseHours(one: Int): Int {
|
||||||
|
return one and 0x1F
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDay(one: Int): Int {
|
||||||
|
return one and 0x1F
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseMonths(first_byte: Int, second_byte: Int): Int {
|
||||||
|
val first_two_bits = first_byte shr 6
|
||||||
|
val second_two_bits = second_byte shr 6
|
||||||
|
return (first_two_bits shl 2) + second_two_bits
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseYear(year: Int): Int {
|
||||||
|
return (year and 0x0F) + 2000
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDate(entry: CGMSHistoryEntry): Long? {
|
||||||
|
if (!entry.entryType.hasDate()) return null
|
||||||
|
val data = entry.datetime
|
||||||
|
return if (entry.entryType.dateType === CGMSHistoryEntryType.DateType.MinuteSpecific) {
|
||||||
|
val atechDateTime = DateTimeUtil.toATechDate(parseYear(data[3].toInt()), parseMonths(data[0].toInt(), data[1].toInt()),
|
||||||
|
parseDay(data[2].toInt()), parseHours(data[0].toInt()), parseMinutes(data[1].toInt()), 0)
|
||||||
|
entry.atechDateTime = atechDateTime
|
||||||
|
atechDateTime
|
||||||
|
} else if (entry.entryType.dateType === CGMSHistoryEntryType.DateType.SecondSpecific) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "parseDate for SecondSpecific type is not implemented.")
|
||||||
|
throw RuntimeException()
|
||||||
|
// return null;
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeGlucoseSensorData(entry: CGMSHistoryEntry) {
|
||||||
|
val sgv = entry.getUnsignedRawDataByIndex(0) * 2
|
||||||
|
entry.addDecodedData("sgv", sgv)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeCalBGForGH(entry: CGMSHistoryEntry) {
|
||||||
|
val amount: Int = entry.getRawDataByIndex(3) and 32 shl 3 or entry.getRawDataByIndexInt(5)
|
||||||
|
//
|
||||||
|
val originType: String
|
||||||
|
originType = when (entry.getRawDataByIndexInt(3) shr 5 and 3) {
|
||||||
|
0x00 -> "rf"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("amount", amount)
|
||||||
|
entry.addDecodedData("originType", originType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorSync(entry: CGMSHistoryEntry) {
|
||||||
|
val syncType: String
|
||||||
|
syncType = when (entry.getRawDataByIndexInt(3) shr 5 and 3) {
|
||||||
|
0x01 -> "new"
|
||||||
|
0x02 -> "old"
|
||||||
|
else -> "find"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("syncType", syncType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorStatus(entry: CGMSHistoryEntry) {
|
||||||
|
val statusType: String
|
||||||
|
statusType = when (entry.getRawDataByIndexInt(3) shr 5 and 3) {
|
||||||
|
0x00 -> "off"
|
||||||
|
0x01 -> "on"
|
||||||
|
0x02 -> "lost"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("statusType", statusType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorCalFactor(entry: CGMSHistoryEntry) {
|
||||||
|
val factor: Double = (entry.getRawDataByIndexInt(5) shl 8 or entry.getRawDataByIndexInt(6)) / 1000.0
|
||||||
|
entry.addDecodedData("factor", factor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorCal(entry: CGMSHistoryEntry) {
|
||||||
|
val calibrationType: String
|
||||||
|
calibrationType = when (entry.getRawDataByIndexInt(1)) {
|
||||||
|
0x00 -> "meter_bg_now"
|
||||||
|
0x01 -> "waiting"
|
||||||
|
0x02 -> "cal_error"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("calibrationType", calibrationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorTimestamp(entry: CGMSHistoryEntry) {
|
||||||
|
val sensorTimestampType: String
|
||||||
|
sensorTimestampType = when (entry.getRawDataByIndex(3).toInt() shr 5 and 3) {
|
||||||
|
0x00 -> "LastRf"
|
||||||
|
0x01 -> "PageEnd"
|
||||||
|
0x02 -> "Gap"
|
||||||
|
else -> "Unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("sensorTimestampType", sensorTimestampType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorPacket(entry: CGMSHistoryEntry) {
|
||||||
|
val packetType: String
|
||||||
|
packetType = when (entry.getRawDataByIndex(1)) {
|
||||||
|
0x02.toByte() -> "init"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("packetType", packetType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeSensorError(entry: CGMSHistoryEntry) {
|
||||||
|
val errorType: String
|
||||||
|
errorType = when (entry.getRawDataByIndexInt(1)) {
|
||||||
|
0x01 -> "end"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
entry.addDecodedData("errorType", errorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDataHighLow(entry: CGMSHistoryEntry, sgv: Int) {
|
||||||
|
entry.addDecodedData("sgv", sgv)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun runPostDecodeTasks() {
|
||||||
|
showStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,724 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusWizardDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.DailyTotalsDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class MedtronicPumpHistoryDecoder extends MedtronicHistoryDecoder<PumpHistoryEntry> {
|
|
||||||
|
|
||||||
private PumpHistoryEntry tbrPreviousRecord;
|
|
||||||
private PumpHistoryEntry changeTimeRecord;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MedtronicPumpHistoryDecoder(
|
|
||||||
AAPSLogger aapsLogger,
|
|
||||||
MedtronicUtil medtronicUtil
|
|
||||||
) {
|
|
||||||
super.aapsLogger = aapsLogger;
|
|
||||||
this.medtronicUtil = medtronicUtil;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<PumpHistoryEntry> createRecords(List<Byte> dataClear) {
|
|
||||||
prepareStatistics();
|
|
||||||
|
|
||||||
int counter = 0;
|
|
||||||
int record = 0;
|
|
||||||
boolean incompletePacket;
|
|
||||||
|
|
||||||
List<PumpHistoryEntry> outList = new ArrayList<>();
|
|
||||||
String skipped = null;
|
|
||||||
|
|
||||||
if (dataClear.size() == 0) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, "Empty page.");
|
|
||||||
return outList;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
int opCode = dataClear.get(counter);
|
|
||||||
boolean special = false;
|
|
||||||
incompletePacket = false;
|
|
||||||
boolean skippedRecords = false;
|
|
||||||
|
|
||||||
if (opCode == 0) {
|
|
||||||
counter++;
|
|
||||||
if (skipped == null)
|
|
||||||
skipped = "0x00";
|
|
||||||
else
|
|
||||||
skipped += " 0x00";
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
if (skipped != null) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, " ... Skipped " + skipped);
|
|
||||||
skipped = null;
|
|
||||||
skippedRecords = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skippedRecords) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, "We had some skipped bytes, which might indicate error in pump history. Please report this problem.");
|
|
||||||
}
|
|
||||||
|
|
||||||
PumpHistoryEntryType entryType = PumpHistoryEntryType.getByCode(opCode);
|
|
||||||
|
|
||||||
PumpHistoryEntry pe = new PumpHistoryEntry();
|
|
||||||
pe.setEntryType(medtronicUtil.getMedtronicPumpModel(), entryType);
|
|
||||||
pe.setOffset(counter);
|
|
||||||
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
if (counter >= 1022) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Byte> listRawData = new ArrayList<>();
|
|
||||||
listRawData.add((byte) opCode);
|
|
||||||
|
|
||||||
if (entryType == PumpHistoryEntryType.UnabsorbedInsulin
|
|
||||||
|| entryType == PumpHistoryEntryType.UnabsorbedInsulin512) {
|
|
||||||
int elements = dataClear.get(counter);
|
|
||||||
listRawData.add((byte) elements);
|
|
||||||
counter++;
|
|
||||||
|
|
||||||
int els = getUnsignedInt(elements);
|
|
||||||
|
|
||||||
for (int k = 0; k < (els - 2); k++) {
|
|
||||||
if (counter < 1022) {
|
|
||||||
listRawData.add(dataClear.get(counter));
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
special = true;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for (int j = 0; j < (entryType.getTotalLength(medtronicUtil.getMedtronicPumpModel()) - 1); j++) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
listRawData.add(dataClear.get(counter));
|
|
||||||
counter++;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, "OpCode: " + ByteUtil.shortHexString((byte) opCode) + ", Invalid package: "
|
|
||||||
+ ByteUtil.getHex(listRawData));
|
|
||||||
// throw ex;
|
|
||||||
incompletePacket = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incompletePacket)
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entryType == PumpHistoryEntryType.None) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, "Error in code. We should have not come into this branch.");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (pe.getEntryType() == PumpHistoryEntryType.UnknownBasePacket) {
|
|
||||||
pe.setOpCode(opCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entryType.getHeadLength(medtronicUtil.getMedtronicPumpModel()) == 0)
|
|
||||||
special = true;
|
|
||||||
|
|
||||||
pe.setData(listRawData, special);
|
|
||||||
|
|
||||||
RecordDecodeStatus decoded = decodeRecord(pe);
|
|
||||||
|
|
||||||
if ((decoded == RecordDecodeStatus.OK) || (decoded == RecordDecodeStatus.Ignored)) {
|
|
||||||
//Log.i(TAG, "#" + record + " " + decoded.getDescription() + " " + pe);
|
|
||||||
} else {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "#" + record + " " + decoded.getDescription() + " " + pe);
|
|
||||||
}
|
|
||||||
|
|
||||||
addToStatistics(pe, decoded, null);
|
|
||||||
|
|
||||||
record++;
|
|
||||||
|
|
||||||
if (decoded == RecordDecodeStatus.OK) // we add only OK records, all others are ignored
|
|
||||||
{
|
|
||||||
outList.add(pe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} while (counter < dataClear.size());
|
|
||||||
|
|
||||||
return outList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public RecordDecodeStatus decodeRecord(PumpHistoryEntry record) {
|
|
||||||
try {
|
|
||||||
return decodeRecord(record, false);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, " Error decoding: type=%s, ex=%s", record.getEntryType().name(), ex.getMessage(), ex));
|
|
||||||
return RecordDecodeStatus.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeRecord(PumpHistoryEntry entry, boolean x) {
|
|
||||||
|
|
||||||
if (entry.getDateTimeLength() > 0) {
|
|
||||||
decodeDateTime(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (entry.getEntryType()) {
|
|
||||||
|
|
||||||
// Valid entries, but not processed
|
|
||||||
case ChangeBasalPattern:
|
|
||||||
case CalBGForPH:
|
|
||||||
case ChangeRemoteId:
|
|
||||||
case ClearAlarm:
|
|
||||||
case ChangeAlarmNotifyMode: // ChangeUtility:
|
|
||||||
case EnableDisableRemote:
|
|
||||||
case BGReceived: // Ian3F: CGMS
|
|
||||||
case SensorAlert: // Ian08 CGMS
|
|
||||||
case ChangeTimeFormat:
|
|
||||||
case ChangeReservoirWarningTime:
|
|
||||||
case ChangeBolusReminderEnable:
|
|
||||||
case SetBolusReminderTime:
|
|
||||||
case ChangeChildBlockEnable:
|
|
||||||
case BolusWizardEnabled:
|
|
||||||
case ChangeBGReminderOffset:
|
|
||||||
case ChangeAlarmClockTime:
|
|
||||||
case ChangeMeterId:
|
|
||||||
case ChangeParadigmID:
|
|
||||||
case JournalEntryMealMarker:
|
|
||||||
case JournalEntryExerciseMarker:
|
|
||||||
case DeleteBolusReminderTime:
|
|
||||||
case SetAutoOff:
|
|
||||||
case SelfTest:
|
|
||||||
case JournalEntryInsulinMarker:
|
|
||||||
case JournalEntryOtherMarker:
|
|
||||||
case BolusWizardSetup512:
|
|
||||||
case ChangeSensorSetup2:
|
|
||||||
case ChangeSensorAlarmSilenceConfig:
|
|
||||||
case ChangeSensorRateOfChangeAlertSetup:
|
|
||||||
case ChangeBolusScrollStepSize:
|
|
||||||
case BolusWizardSetup:
|
|
||||||
case ChangeVariableBolus:
|
|
||||||
case ChangeAudioBolus:
|
|
||||||
case ChangeBGReminderEnable:
|
|
||||||
case ChangeAlarmClockEnable:
|
|
||||||
case BolusReminder:
|
|
||||||
case DeleteAlarmClockTime:
|
|
||||||
case ChangeCarbUnits:
|
|
||||||
case ChangeWatchdogEnable:
|
|
||||||
case ChangeOtherDeviceID:
|
|
||||||
case ReadOtherDevicesIDs:
|
|
||||||
case BGReceived512:
|
|
||||||
case SensorStatus:
|
|
||||||
case ReadCaptureEventEnabled:
|
|
||||||
case ChangeCaptureEventEnable:
|
|
||||||
case ReadOtherDevicesStatus:
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case Sensor_0x54:
|
|
||||||
case Sensor_0x55:
|
|
||||||
case Sensor_0x51:
|
|
||||||
case Sensor_0x52:
|
|
||||||
// case EventUnknown_MM522_0x45:
|
|
||||||
// case EventUnknown_MM522_0x46:
|
|
||||||
// case EventUnknown_MM522_0x47:
|
|
||||||
// case EventUnknown_MM522_0x48:
|
|
||||||
// case EventUnknown_MM522_0x49:
|
|
||||||
// case EventUnknown_MM522_0x4a:
|
|
||||||
// case EventUnknown_MM522_0x4b:
|
|
||||||
// case EventUnknown_MM522_0x4c:
|
|
||||||
// case EventUnknown_MM512_0x10:
|
|
||||||
case EventUnknown_MM512_0x2e:
|
|
||||||
// case EventUnknown_MM512_0x37:
|
|
||||||
// case EventUnknown_MM512_0x38:
|
|
||||||
// case EventUnknown_MM512_0x4e:
|
|
||||||
// case EventUnknown_MM522_0x70:
|
|
||||||
// case EventUnknown_MM512_0x88:
|
|
||||||
// case EventUnknown_MM512_0x94:
|
|
||||||
// case EventUnknown_MM522_0xE8:
|
|
||||||
// case EventUnknown_0x4d:
|
|
||||||
// case EventUnknown_MM522_0x25:
|
|
||||||
// case EventUnknown_MM522_0x05:
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, " -- ignored Unknown Pump Entry: " + entry);
|
|
||||||
return RecordDecodeStatus.Ignored;
|
|
||||||
|
|
||||||
case UnabsorbedInsulin:
|
|
||||||
case UnabsorbedInsulin512:
|
|
||||||
return RecordDecodeStatus.Ignored;
|
|
||||||
|
|
||||||
// **** Implemented records ****
|
|
||||||
|
|
||||||
case DailyTotals522:
|
|
||||||
case DailyTotals523:
|
|
||||||
case DailyTotals515:
|
|
||||||
case EndResultTotals:
|
|
||||||
return decodeDailyTotals(entry);
|
|
||||||
|
|
||||||
case ChangeBasalProfile_OldProfile:
|
|
||||||
case ChangeBasalProfile_NewProfile:
|
|
||||||
return decodeBasalProfile(entry);
|
|
||||||
|
|
||||||
case BasalProfileStart:
|
|
||||||
return decodeBasalProfileStart(entry);
|
|
||||||
|
|
||||||
case ChangeTime:
|
|
||||||
changeTimeRecord = entry;
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case NewTimeSet:
|
|
||||||
decodeChangeTime(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case TempBasalDuration:
|
|
||||||
// decodeTempBasal(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case TempBasalRate:
|
|
||||||
// decodeTempBasal(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case Bolus:
|
|
||||||
decodeBolus(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case BatteryChange:
|
|
||||||
decodeBatteryActivity(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case LowReservoir:
|
|
||||||
decodeLowReservoir(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case LowBattery:
|
|
||||||
case SuspendPump:
|
|
||||||
case ResumePump:
|
|
||||||
case Rewind:
|
|
||||||
case NoDeliveryAlarm:
|
|
||||||
case ChangeTempBasalType:
|
|
||||||
case ChangeMaxBolus:
|
|
||||||
case ChangeMaxBasal:
|
|
||||||
case ClearSettings:
|
|
||||||
case SaveSettings:
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case BolusWizard:
|
|
||||||
return decodeBolusWizard(entry);
|
|
||||||
|
|
||||||
case BolusWizard512:
|
|
||||||
return decodeBolusWizard512(entry);
|
|
||||||
|
|
||||||
case Prime:
|
|
||||||
decodePrime(entry);
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
|
|
||||||
case TempBasalCombined:
|
|
||||||
return RecordDecodeStatus.Ignored;
|
|
||||||
|
|
||||||
case None:
|
|
||||||
case UnknownBasePacket:
|
|
||||||
return RecordDecodeStatus.Error;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Not supported: " + entry.getEntryType());
|
|
||||||
return RecordDecodeStatus.NotSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// return RecordDecodeStatus.Error;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeDailyTotals(PumpHistoryEntry entry) {
|
|
||||||
|
|
||||||
entry.addDecodedData("Raw Data", ByteUtil.getHex(entry.getRawData()));
|
|
||||||
|
|
||||||
DailyTotalsDTO totals = new DailyTotalsDTO(entry);
|
|
||||||
|
|
||||||
entry.addDecodedData("Object", totals);
|
|
||||||
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeBasalProfile(PumpHistoryEntry entry) {
|
|
||||||
|
|
||||||
// LOG.debug("decodeBasalProfile: {}", entry);
|
|
||||||
|
|
||||||
BasalProfile basalProfile = new BasalProfile(aapsLogger);
|
|
||||||
basalProfile.setRawDataFromHistory(entry.getBody());
|
|
||||||
|
|
||||||
// LOG.debug("decodeBasalProfile BasalProfile: {}", basalProfile);
|
|
||||||
|
|
||||||
entry.addDecodedData("Object", basalProfile);
|
|
||||||
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeChangeTime(PumpHistoryEntry entry) {
|
|
||||||
if (changeTimeRecord == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
entry.setDisplayableValue(entry.getDateTimeString());
|
|
||||||
|
|
||||||
this.changeTimeRecord = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeBatteryActivity(PumpHistoryEntry entry) {
|
|
||||||
// this.writeData(PumpBaseType.Event, entry.getHead()[0] == 0 ? PumpEventType.BatteryRemoved :
|
|
||||||
// PumpEventType.BatteryReplaced, entry.getATechDate());
|
|
||||||
|
|
||||||
entry.setDisplayableValue(entry.getHead()[0] == 0 ? "Battery Removed" : "Battery Replaced");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static String getFormattedValue(float value, int decimals) {
|
|
||||||
return String.format(Locale.ENGLISH, "%." + decimals + "f", value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeBasalProfileStart(PumpHistoryEntry entry) {
|
|
||||||
byte[] body = entry.getBody();
|
|
||||||
// int bodyOffset = headerSize + timestampSize;
|
|
||||||
int offset = body[0] * 1000 * 30 * 60;
|
|
||||||
Float rate = null;
|
|
||||||
int index = entry.getHead()[0];
|
|
||||||
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) {
|
|
||||||
rate = body[1] * 0.025f;
|
|
||||||
}
|
|
||||||
|
|
||||||
//LOG.info("Basal Profile Start: offset={}, rate={}, index={}, body_raw={}", offset, rate, index, body);
|
|
||||||
|
|
||||||
if (rate == null) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Basal Profile Start (ERROR): offset=%d, rate=%.3f, index=%d, body_raw=%s", offset, rate, index, ByteUtil.getHex(body)));
|
|
||||||
return RecordDecodeStatus.Error;
|
|
||||||
} else {
|
|
||||||
entry.addDecodedData("Value", getFormattedFloat(rate, 3));
|
|
||||||
entry.setDisplayableValue(getFormattedFloat(rate, 3));
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeBolusWizard(PumpHistoryEntry entry) {
|
|
||||||
byte[] body = entry.getBody();
|
|
||||||
|
|
||||||
BolusWizardDTO dto = new BolusWizardDTO();
|
|
||||||
|
|
||||||
float bolusStrokes = 10.0f;
|
|
||||||
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) {
|
|
||||||
// https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/log_entries/bolus_wizard.rb#L102
|
|
||||||
bolusStrokes = 40.0f;
|
|
||||||
|
|
||||||
dto.carbs = ((body[1] & 0x0c) << 6) + body[0];
|
|
||||||
|
|
||||||
dto.bloodGlucose = ((body[1] & 0x03) << 8) + entry.getHead()[0];
|
|
||||||
dto.carbRatio = body[1] / 10.0f;
|
|
||||||
// carb_ratio (?) = (((self.body[2] & 0x07) << 8) + self.body[3]) /
|
|
||||||
// 10.0s
|
|
||||||
dto.insulinSensitivity = new Float(body[4]);
|
|
||||||
dto.bgTargetLow = (int) body[5];
|
|
||||||
dto.bgTargetHigh = (int) body[14];
|
|
||||||
dto.correctionEstimate = (((body[9] & 0x38) << 5) + body[6]) / bolusStrokes;
|
|
||||||
dto.foodEstimate = ((body[7] << 8) + body[8]) / bolusStrokes;
|
|
||||||
dto.unabsorbedInsulin = ((body[10] << 8) + body[11]) / bolusStrokes;
|
|
||||||
dto.bolusTotal = ((body[12] << 8) + body[13]) / bolusStrokes;
|
|
||||||
} else {
|
|
||||||
dto.bloodGlucose = (((body[1] & 0x0F) << 8) | entry.getHead()[0]);
|
|
||||||
dto.carbs = (int) body[0];
|
|
||||||
dto.carbRatio = Float.valueOf(body[2]);
|
|
||||||
dto.insulinSensitivity = new Float(body[3]);
|
|
||||||
dto.bgTargetLow = (int) body[4];
|
|
||||||
dto.bgTargetHigh = (int) body[12];
|
|
||||||
dto.bolusTotal = body[11] / bolusStrokes;
|
|
||||||
dto.foodEstimate = body[6] / bolusStrokes;
|
|
||||||
dto.unabsorbedInsulin = body[9] / bolusStrokes;
|
|
||||||
dto.bolusTotal = body[11] / bolusStrokes;
|
|
||||||
dto.correctionEstimate = (body[7] + (body[5] & 0x0F)) / bolusStrokes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dto.bloodGlucose != null && dto.bloodGlucose < 0) {
|
|
||||||
dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.byteValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.atechDateTime = entry.atechDateTime;
|
|
||||||
entry.addDecodedData("Object", dto);
|
|
||||||
entry.setDisplayableValue(dto.getDisplayableValue());
|
|
||||||
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private RecordDecodeStatus decodeBolusWizard512(PumpHistoryEntry entry) {
|
|
||||||
byte[] body = entry.getBody();
|
|
||||||
|
|
||||||
BolusWizardDTO dto = new BolusWizardDTO();
|
|
||||||
|
|
||||||
float bolusStrokes = 10.0f;
|
|
||||||
|
|
||||||
dto.bloodGlucose = ((body[1] & 0x03 << 8) | entry.getHead()[0]);
|
|
||||||
dto.carbs = (body[1] & 0xC) << 6 | body[0]; // (int)body[0];
|
|
||||||
dto.carbRatio = Float.valueOf(body[2]);
|
|
||||||
dto.insulinSensitivity = new Float(body[3]);
|
|
||||||
dto.bgTargetLow = (int) body[4];
|
|
||||||
dto.foodEstimate = body[6] / 10.0f;
|
|
||||||
dto.correctionEstimate = (body[7] + (body[5] & 0x0F)) / bolusStrokes;
|
|
||||||
dto.unabsorbedInsulin = body[9] / bolusStrokes;
|
|
||||||
dto.bolusTotal = body[11] / bolusStrokes;
|
|
||||||
dto.bgTargetHigh = dto.bgTargetLow;
|
|
||||||
|
|
||||||
if (dto.bloodGlucose != null && dto.bloodGlucose < 0) {
|
|
||||||
dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.byteValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.atechDateTime = entry.atechDateTime;
|
|
||||||
entry.addDecodedData("Object", dto);
|
|
||||||
entry.setDisplayableValue(dto.getDisplayableValue());
|
|
||||||
|
|
||||||
return RecordDecodeStatus.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeLowReservoir(PumpHistoryEntry entry) {
|
|
||||||
float amount = (getUnsignedInt(entry.getHead()[0]) * 1.0f / 10.0f) * 2;
|
|
||||||
|
|
||||||
entry.setDisplayableValue(getFormattedValue(amount, 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodePrime(PumpHistoryEntry entry) {
|
|
||||||
float amount = ByteUtil.toInt(entry.getHead()[2], entry.getHead()[3]) / 10.0f;
|
|
||||||
float fixed = ByteUtil.toInt(entry.getHead()[0], entry.getHead()[1]) / 10.0f;
|
|
||||||
|
|
||||||
// amount = (double)(asUINT8(data[4]) << 2) / 40.0;
|
|
||||||
// programmedAmount = (double)(asUINT8(data[2]) << 2) / 40.0;
|
|
||||||
// primeType = programmedAmount == 0 ? "manual" : "fixed";
|
|
||||||
|
|
||||||
entry.addDecodedData("Amount", amount);
|
|
||||||
entry.addDecodedData("FixedAmount", fixed);
|
|
||||||
|
|
||||||
entry.setDisplayableValue("Amount=" + getFormattedValue(amount, 2) + ", Fixed Amount="
|
|
||||||
+ getFormattedValue(fixed, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeChangeTempBasalType(PumpHistoryEntry entry) {
|
|
||||||
entry.addDecodedData("isPercent", ByteUtil.asUINT8(entry.getRawDataByIndex(0)) == 1); // index moved from 1 -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeBgReceived(PumpHistoryEntry entry) {
|
|
||||||
entry.addDecodedData("amount", (ByteUtil.asUINT8(entry.getRawDataByIndex(0)) << 3) + (ByteUtil.asUINT8(entry.getRawDataByIndex(3)) >> 5));
|
|
||||||
entry.addDecodedData("meter", ByteUtil.substring(entry.getRawData(), 6, 3)); // index moved from 1 -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeCalBGForPH(PumpHistoryEntry entry) {
|
|
||||||
entry.addDecodedData("amount", ((ByteUtil.asUINT8(entry.getRawDataByIndex(5)) & 0x80) << 1) + ByteUtil.asUINT8(entry.getRawDataByIndex(0))); // index moved from 1 -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeNoDeliveryAlarm(PumpHistoryEntry entry) {
|
|
||||||
//rawtype = asUINT8(data[1]);
|
|
||||||
// not sure if this is actually NoDelivery Alarm?
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postProcess() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void runPostDecodeTasks() {
|
|
||||||
this.showStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeBolus(PumpHistoryEntry entry) {
|
|
||||||
BolusDTO bolus = new BolusDTO();
|
|
||||||
|
|
||||||
byte[] data = entry.getHead();
|
|
||||||
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) {
|
|
||||||
bolus.setRequestedAmount(ByteUtil.toInt(data[0], data[1]) / 40.0d);
|
|
||||||
bolus.setDeliveredAmount(ByteUtil.toInt(data[2], data[3]) / 40.0d);
|
|
||||||
bolus.setInsulinOnBoard(ByteUtil.toInt(data[4], data[5]) / 40.0d);
|
|
||||||
bolus.setDuration(data[6] * 30);
|
|
||||||
} else {
|
|
||||||
bolus.setRequestedAmount(ByteUtil.asUINT8(data[0]) / 10.0d);
|
|
||||||
bolus.setDeliveredAmount(ByteUtil.asUINT8(data[1]) / 10.0d);
|
|
||||||
bolus.setDuration(ByteUtil.asUINT8(data[2]) * 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
bolus.setBolusType((bolus.getDuration() != null && (bolus.getDuration() > 0)) ? PumpBolusType.Extended
|
|
||||||
: PumpBolusType.Normal);
|
|
||||||
bolus.setAtechDateTime(entry.atechDateTime);
|
|
||||||
|
|
||||||
entry.addDecodedData("Object", bolus);
|
|
||||||
entry.setDisplayableValue(bolus.getDisplayableValue());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeTempBasal(PumpHistoryEntry entry) {
|
|
||||||
|
|
||||||
if (this.tbrPreviousRecord == null) {
|
|
||||||
// LOG.debug(this.tbrPreviousRecord.toString());
|
|
||||||
this.tbrPreviousRecord = entry;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeTempBasal(this.tbrPreviousRecord, entry);
|
|
||||||
|
|
||||||
tbrPreviousRecord = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void decodeTempBasal(PumpHistoryEntry tbrPreviousRecord, PumpHistoryEntry entry) {
|
|
||||||
|
|
||||||
PumpHistoryEntry tbrRate = null, tbrDuration = null;
|
|
||||||
|
|
||||||
if (entry.getEntryType() == PumpHistoryEntryType.TempBasalRate) {
|
|
||||||
tbrRate = entry;
|
|
||||||
} else {
|
|
||||||
tbrDuration = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tbrRate != null) {
|
|
||||||
tbrDuration = tbrPreviousRecord;
|
|
||||||
} else {
|
|
||||||
tbrRate = tbrPreviousRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempBasalPair tbr = new TempBasalPair(
|
|
||||||
// tbrRate.getHead()[0],
|
|
||||||
// tbrDuration.getHead()[0],
|
|
||||||
// (ByteUtil.asUINT8(tbrRate.getDatetime()[4]) >> 3) == 0);
|
|
||||||
|
|
||||||
TempBasalPair tbr = new TempBasalPair(
|
|
||||||
tbrRate.getHead()[0],
|
|
||||||
tbrRate.getBody()[0],
|
|
||||||
tbrDuration.getHead()[0],
|
|
||||||
(ByteUtil.asUINT8(tbrRate.getDatetime()[4]) >> 3) == 0);
|
|
||||||
|
|
||||||
// System.out.println("TBR: amount=" + tbr.getInsulinRate() + ", duration=" + tbr.getDurationMinutes()
|
|
||||||
// // + " min. Packed: " + tbr.getValue()
|
|
||||||
// );
|
|
||||||
|
|
||||||
entry.addDecodedData("Object", tbr);
|
|
||||||
entry.setDisplayableValue(tbr.getDescription());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeDateTime(PumpHistoryEntry entry) {
|
|
||||||
byte[] dt = entry.getDatetime();
|
|
||||||
|
|
||||||
if (dt == null) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "DateTime not set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.getDateTimeLength() == 5) {
|
|
||||||
|
|
||||||
int seconds = dt[0] & 0x3F;
|
|
||||||
int minutes = dt[1] & 0x3F;
|
|
||||||
int hour = dt[2] & 0x1F;
|
|
||||||
|
|
||||||
int month = ((dt[0] >> 4) & 0x0c) + ((dt[1] >> 6) & 0x03);
|
|
||||||
// ((dt[0] & 0xC0) >> 6) | ((dt[1] & 0xC0) >> 4);
|
|
||||||
|
|
||||||
int dayOfMonth = dt[3] & 0x1F;
|
|
||||||
int year = fix2DigitYear(dt[4] & 0x3F); // Assuming this is correct, need to verify. Otherwise this will be
|
|
||||||
// a problem in 2016.
|
|
||||||
|
|
||||||
entry.setAtechDateTime(DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds));
|
|
||||||
|
|
||||||
} else if (entry.getDateTimeLength() == 2) {
|
|
||||||
int low = ByteUtil.asUINT8(dt[0]) & 0x1F;
|
|
||||||
int mhigh = (ByteUtil.asUINT8(dt[0]) & 0xE0) >> 4;
|
|
||||||
int mlow = (ByteUtil.asUINT8(dt[1]) & 0x80) >> 7;
|
|
||||||
int month = mhigh + mlow;
|
|
||||||
// int dayOfMonth = low + 1;
|
|
||||||
int dayOfMonth = dt[0] & 0x1F;
|
|
||||||
int year = 2000 + (ByteUtil.asUINT8(dt[1]) & 0x7F);
|
|
||||||
|
|
||||||
int hour = 0;
|
|
||||||
int minutes = 0;
|
|
||||||
int seconds = 0;
|
|
||||||
|
|
||||||
//LOG.debug("DT: {} {} {}", year, month, dayOfMonth);
|
|
||||||
|
|
||||||
if (dayOfMonth == 32) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Entry: Day 32 %s = [%s] %s", entry.getEntryType().name(),
|
|
||||||
ByteUtil.getHex(entry.getRawData()), entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEndResults(entry.getEntryType())) {
|
|
||||||
hour = 23;
|
|
||||||
minutes = 59;
|
|
||||||
seconds = 59;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.setAtechDateTime(DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Unknown datetime format: " + entry.getDateTimeLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isEndResults(PumpHistoryEntryType entryType) {
|
|
||||||
|
|
||||||
return (entryType == PumpHistoryEntryType.EndResultTotals ||
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals515 ||
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals522 ||
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals523);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private int fix2DigitYear(int year) {
|
|
||||||
if (year > 90) {
|
|
||||||
year += 1900;
|
|
||||||
} else {
|
|
||||||
year += 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return year;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,534 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntryType.Companion.getByCode
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusWizardDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.DailyTotalsDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.experimental.and
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class MedtronicPumpHistoryDecoder @Inject constructor(
|
||||||
|
aapsLogger: AAPSLogger,
|
||||||
|
medtronicUtil: MedtronicUtil,
|
||||||
|
bitUtils: ByteUtil
|
||||||
|
) : MedtronicHistoryDecoder<PumpHistoryEntry>(aapsLogger, medtronicUtil, bitUtils) {
|
||||||
|
|
||||||
|
//private var tbrPreviousRecord: PumpHistoryEntry? = null
|
||||||
|
private var changeTimeRecord: PumpHistoryEntry? = null
|
||||||
|
|
||||||
|
override fun createRecords(dataClearInput: MutableList<Byte>): MutableList<PumpHistoryEntry> {
|
||||||
|
prepareStatistics()
|
||||||
|
var counter = 0
|
||||||
|
var record = 0
|
||||||
|
var incompletePacket: Boolean
|
||||||
|
val outList: MutableList<PumpHistoryEntry> = mutableListOf()
|
||||||
|
var skipped: String? = null
|
||||||
|
if (dataClearInput.size == 0) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "Empty page.")
|
||||||
|
return outList
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
val opCode: Int = dataClearInput[counter].toInt()
|
||||||
|
var special = false
|
||||||
|
incompletePacket = false
|
||||||
|
var skippedRecords = false
|
||||||
|
if (opCode == 0) {
|
||||||
|
counter++
|
||||||
|
if (skipped == null) skipped = "0x00" else skipped += " 0x00"
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if (skipped != null) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, " ... Skipped $skipped")
|
||||||
|
skipped = null
|
||||||
|
skippedRecords = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (skippedRecords) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "We had some skipped bytes, which might indicate error in pump history. Please report this problem.")
|
||||||
|
}
|
||||||
|
val entryType = getByCode(opCode.toByte())
|
||||||
|
val pe = PumpHistoryEntry()
|
||||||
|
pe.setEntryType(medtronicUtil.medtronicPumpModel, entryType, if (entryType == PumpHistoryEntryType.UnknownBasePacket) opCode.toByte() else null)
|
||||||
|
pe.offset = counter
|
||||||
|
counter++
|
||||||
|
if (counter >= 1022) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val listRawData: MutableList<Byte> = ArrayList()
|
||||||
|
listRawData.add(opCode.toByte())
|
||||||
|
if (entryType === PumpHistoryEntryType.UnabsorbedInsulin
|
||||||
|
|| entryType === PumpHistoryEntryType.UnabsorbedInsulin512) {
|
||||||
|
val elements: Int = dataClearInput[counter].toInt()
|
||||||
|
listRawData.add(elements.toByte())
|
||||||
|
counter++
|
||||||
|
val els = getUnsignedInt(elements)
|
||||||
|
for (k in 0 until els - 2) {
|
||||||
|
if (counter < 1022) {
|
||||||
|
listRawData.add(dataClearInput[counter])
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
special = true
|
||||||
|
} else {
|
||||||
|
for (j in 0 until entryType.getTotalLength(medtronicUtil.medtronicPumpModel) - 1) {
|
||||||
|
try {
|
||||||
|
listRawData.add(dataClearInput[counter])
|
||||||
|
counter++
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "OpCode: " + ByteUtil.shortHexString(opCode.toByte()) + ", Invalid package: "
|
||||||
|
+ ByteUtil.getHex(listRawData))
|
||||||
|
// throw ex;
|
||||||
|
incompletePacket = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (incompletePacket) break
|
||||||
|
}
|
||||||
|
if (entryType === PumpHistoryEntryType.None) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, "Error in code. We should have not come into this branch.")
|
||||||
|
} else {
|
||||||
|
if (pe.entryType === PumpHistoryEntryType.UnknownBasePacket) {
|
||||||
|
pe.opCode = opCode.toByte()
|
||||||
|
}
|
||||||
|
if (entryType.getHeadLength(medtronicUtil.medtronicPumpModel) == 0) special = true
|
||||||
|
pe.setData(listRawData, special)
|
||||||
|
val decoded = decodeRecord(pe)
|
||||||
|
if (decoded === RecordDecodeStatus.OK || decoded === RecordDecodeStatus.Ignored) {
|
||||||
|
//Log.i(TAG, "#" + record + " " + decoded.getDescription() + " " + pe);
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "#" + record + " " + decoded.description + " " + pe)
|
||||||
|
}
|
||||||
|
addToStatistics(pe, decoded, null)
|
||||||
|
record++
|
||||||
|
if (decoded === RecordDecodeStatus.OK) // we add only OK records, all others are ignored
|
||||||
|
{
|
||||||
|
outList.add(pe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (counter < dataClearInput.size)
|
||||||
|
return outList
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decodeRecord(record: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
return try {
|
||||||
|
decodeRecordInternal(record)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, " Error decoding: type=%s, ex=%s", record.entryType.name, ex.message, ex))
|
||||||
|
//ex.printStackTrace()
|
||||||
|
RecordDecodeStatus.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeRecordInternal(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
if (entry.dateTimeLength > 0) {
|
||||||
|
decodeDateTime(entry)
|
||||||
|
}
|
||||||
|
return when (entry.entryType) {
|
||||||
|
PumpHistoryEntryType.ChangeBasalPattern,
|
||||||
|
PumpHistoryEntryType.CalBGForPH,
|
||||||
|
PumpHistoryEntryType.ChangeRemoteId,
|
||||||
|
PumpHistoryEntryType.ClearAlarm,
|
||||||
|
PumpHistoryEntryType.ChangeAlarmNotifyMode,
|
||||||
|
PumpHistoryEntryType.EnableDisableRemote,
|
||||||
|
PumpHistoryEntryType.BGReceived,
|
||||||
|
PumpHistoryEntryType.SensorAlert,
|
||||||
|
PumpHistoryEntryType.ChangeTimeFormat,
|
||||||
|
PumpHistoryEntryType.ChangeReservoirWarningTime,
|
||||||
|
PumpHistoryEntryType.ChangeBolusReminderEnable,
|
||||||
|
PumpHistoryEntryType.SetBolusReminderTime,
|
||||||
|
PumpHistoryEntryType.ChangeChildBlockEnable,
|
||||||
|
PumpHistoryEntryType.BolusWizardEnabled,
|
||||||
|
PumpHistoryEntryType.ChangeBGReminderOffset,
|
||||||
|
PumpHistoryEntryType.ChangeAlarmClockTime,
|
||||||
|
PumpHistoryEntryType.ChangeMeterId,
|
||||||
|
PumpHistoryEntryType.ChangeParadigmID,
|
||||||
|
PumpHistoryEntryType.JournalEntryMealMarker,
|
||||||
|
PumpHistoryEntryType.JournalEntryExerciseMarker,
|
||||||
|
PumpHistoryEntryType.DeleteBolusReminderTime,
|
||||||
|
PumpHistoryEntryType.SetAutoOff,
|
||||||
|
PumpHistoryEntryType.SelfTest,
|
||||||
|
PumpHistoryEntryType.JournalEntryInsulinMarker,
|
||||||
|
PumpHistoryEntryType.JournalEntryOtherMarker,
|
||||||
|
PumpHistoryEntryType.BolusWizardSetup512,
|
||||||
|
PumpHistoryEntryType.ChangeSensorSetup2,
|
||||||
|
PumpHistoryEntryType.ChangeSensorAlarmSilenceConfig,
|
||||||
|
PumpHistoryEntryType.ChangeSensorRateOfChangeAlertSetup,
|
||||||
|
PumpHistoryEntryType.ChangeBolusScrollStepSize,
|
||||||
|
PumpHistoryEntryType.BolusWizardSetup,
|
||||||
|
PumpHistoryEntryType.ChangeVariableBolus,
|
||||||
|
PumpHistoryEntryType.ChangeAudioBolus,
|
||||||
|
PumpHistoryEntryType.ChangeBGReminderEnable,
|
||||||
|
PumpHistoryEntryType.ChangeAlarmClockEnable,
|
||||||
|
PumpHistoryEntryType.BolusReminder,
|
||||||
|
PumpHistoryEntryType.DeleteAlarmClockTime,
|
||||||
|
PumpHistoryEntryType.ChangeCarbUnits,
|
||||||
|
PumpHistoryEntryType.ChangeWatchdogEnable,
|
||||||
|
PumpHistoryEntryType.ChangeOtherDeviceID,
|
||||||
|
PumpHistoryEntryType.ReadOtherDevicesIDs,
|
||||||
|
PumpHistoryEntryType.BGReceived512,
|
||||||
|
PumpHistoryEntryType.SensorStatus,
|
||||||
|
PumpHistoryEntryType.ReadCaptureEventEnabled,
|
||||||
|
PumpHistoryEntryType.ChangeCaptureEventEnable,
|
||||||
|
PumpHistoryEntryType.ReadOtherDevicesStatus -> RecordDecodeStatus.OK
|
||||||
|
|
||||||
|
PumpHistoryEntryType.Sensor_0x54,
|
||||||
|
PumpHistoryEntryType.Sensor_0x55,
|
||||||
|
PumpHistoryEntryType.Sensor_0x51,
|
||||||
|
PumpHistoryEntryType.Sensor_0x52,
|
||||||
|
PumpHistoryEntryType.EventUnknown_MM512_0x2e -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, " -- ignored Unknown Pump Entry: $entry")
|
||||||
|
RecordDecodeStatus.Ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.UnabsorbedInsulin,
|
||||||
|
PumpHistoryEntryType.UnabsorbedInsulin512 -> RecordDecodeStatus.Ignored
|
||||||
|
PumpHistoryEntryType.DailyTotals522,
|
||||||
|
PumpHistoryEntryType.DailyTotals523,
|
||||||
|
PumpHistoryEntryType.DailyTotals515,
|
||||||
|
PumpHistoryEntryType.EndResultTotals -> decodeDailyTotals(entry)
|
||||||
|
PumpHistoryEntryType.ChangeBasalProfile_OldProfile,
|
||||||
|
PumpHistoryEntryType.ChangeBasalProfile_NewProfile -> decodeBasalProfile(entry)
|
||||||
|
PumpHistoryEntryType.BasalProfileStart -> decodeBasalProfileStart(entry)
|
||||||
|
|
||||||
|
PumpHistoryEntryType.ChangeTime -> {
|
||||||
|
changeTimeRecord = entry
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.NewTimeSet -> {
|
||||||
|
decodeChangeTime(entry)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.TempBasalDuration -> // decodeTempBasal(entry);
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
|
||||||
|
PumpHistoryEntryType.TempBasalRate -> // decodeTempBasal(entry);
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
|
||||||
|
PumpHistoryEntryType.Bolus -> {
|
||||||
|
decodeBolus(entry)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.BatteryChange -> {
|
||||||
|
decodeBatteryActivity(entry)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.LowReservoir -> {
|
||||||
|
decodeLowReservoir(entry)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.LowBattery,
|
||||||
|
PumpHistoryEntryType.SuspendPump,
|
||||||
|
PumpHistoryEntryType.ResumePump,
|
||||||
|
PumpHistoryEntryType.Rewind,
|
||||||
|
PumpHistoryEntryType.NoDeliveryAlarm,
|
||||||
|
PumpHistoryEntryType.ChangeTempBasalType,
|
||||||
|
PumpHistoryEntryType.ChangeMaxBolus,
|
||||||
|
PumpHistoryEntryType.ChangeMaxBasal,
|
||||||
|
PumpHistoryEntryType.ClearSettings,
|
||||||
|
PumpHistoryEntryType.SaveSettings -> RecordDecodeStatus.OK
|
||||||
|
PumpHistoryEntryType.BolusWizard -> decodeBolusWizard(entry)
|
||||||
|
PumpHistoryEntryType.BolusWizard512 -> decodeBolusWizard512(entry)
|
||||||
|
|
||||||
|
PumpHistoryEntryType.Prime -> {
|
||||||
|
decodePrime(entry)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
PumpHistoryEntryType.TempBasalCombined -> RecordDecodeStatus.Ignored
|
||||||
|
PumpHistoryEntryType.None, PumpHistoryEntryType.UnknownBasePacket -> RecordDecodeStatus.Error
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Not supported: " + entry.entryType)
|
||||||
|
RecordDecodeStatus.NotSupported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDailyTotals(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
entry.addDecodedData("Raw Data", ByteUtil.getHex(entry.rawData))
|
||||||
|
val totals = DailyTotalsDTO(entry)
|
||||||
|
entry.addDecodedData("Object", totals)
|
||||||
|
return RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBasalProfile(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
val basalProfile = BasalProfile(aapsLogger)
|
||||||
|
basalProfile.setRawDataFromHistory(entry.body)
|
||||||
|
entry.addDecodedData("Object", basalProfile)
|
||||||
|
return RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeChangeTime(entry: PumpHistoryEntry) {
|
||||||
|
if (changeTimeRecord == null) return
|
||||||
|
entry.displayableValue = entry.dateTimeString
|
||||||
|
changeTimeRecord = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBatteryActivity(entry: PumpHistoryEntry) {
|
||||||
|
entry.displayableValue = if (entry.head[0] == 0.toByte()) "Battery Removed" else "Battery Replaced"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBasalProfileStart(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
val body = entry.body
|
||||||
|
val offset = body[0] * 1000 * 30 * 60
|
||||||
|
var rate: Float? = null
|
||||||
|
val index = entry.head[0].toInt()
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) {
|
||||||
|
rate = body[1] * 0.025f
|
||||||
|
}
|
||||||
|
|
||||||
|
//LOG.info("Basal Profile Start: offset={}, rate={}, index={}, body_raw={}", offset, rate, index, body);
|
||||||
|
return if (rate == null) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Basal Profile Start (ERROR): offset=%d, rate=%.3f, index=%d, body_raw=%s", offset, rate, index, ByteUtil.getHex(body)))
|
||||||
|
RecordDecodeStatus.Error
|
||||||
|
} else {
|
||||||
|
entry.addDecodedData("Value", getFormattedFloat(rate, 3))
|
||||||
|
entry.displayableValue = getFormattedFloat(rate, 3)
|
||||||
|
RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBolusWizard(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
val body = entry.body
|
||||||
|
val dto = BolusWizardDTO()
|
||||||
|
var bolusStrokes = 10.0f
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) {
|
||||||
|
// https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/log_entries/bolus_wizard.rb#L102
|
||||||
|
bolusStrokes = 40.0f
|
||||||
|
dto.carbs = ((body[1] and 0x0c.toByte()).toInt() shl 6) + body[0]
|
||||||
|
dto.bloodGlucose = ((body[1] and 0x03).toInt() shl 8) + entry.head[0]
|
||||||
|
dto.carbRatio = body[1] / 10.0f
|
||||||
|
// carb_ratio (?) = (((self.body[2] & 0x07) << 8) + self.body[3]) /
|
||||||
|
// 10.0s
|
||||||
|
dto.insulinSensitivity = body[4].toFloat()
|
||||||
|
dto.bgTargetLow = body[5].toInt()
|
||||||
|
dto.bgTargetHigh = body[14].toInt()
|
||||||
|
dto.correctionEstimate = (((body[9] and 0x38).toInt() shl 5) + body[6]) / bolusStrokes
|
||||||
|
dto.foodEstimate = ((body[7].toInt() shl 8) + body[8]) / bolusStrokes
|
||||||
|
dto.unabsorbedInsulin = ((body[10].toInt() shl 8) + body[11]) / bolusStrokes
|
||||||
|
dto.bolusTotal = ((body[12].toInt() shl 8) + body[13]) / bolusStrokes
|
||||||
|
} else {
|
||||||
|
dto.bloodGlucose = (body.get(1) and 0x0F).toInt() shl 8 or entry.head.get(0).toInt()
|
||||||
|
dto.carbs = body[0].toInt()
|
||||||
|
dto.carbRatio = body[2].toFloat()
|
||||||
|
dto.insulinSensitivity = body[3].toFloat()
|
||||||
|
dto.bgTargetLow = body.get(4).toInt()
|
||||||
|
dto.bgTargetHigh = body.get(12).toInt()
|
||||||
|
dto.bolusTotal = body.get(11) / bolusStrokes
|
||||||
|
dto.foodEstimate = body.get(6) / bolusStrokes
|
||||||
|
dto.unabsorbedInsulin = body.get(9) / bolusStrokes
|
||||||
|
dto.bolusTotal = body.get(11) / bolusStrokes
|
||||||
|
dto.correctionEstimate = (body.get(7) + (body.get(5) and 0x0F)) / bolusStrokes
|
||||||
|
}
|
||||||
|
if (dto.bloodGlucose < 0) {
|
||||||
|
dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.toByte())
|
||||||
|
}
|
||||||
|
dto.atechDateTime = entry.atechDateTime
|
||||||
|
entry.addDecodedData("Object", dto)
|
||||||
|
entry.displayableValue = dto.displayableValue
|
||||||
|
return RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBolusWizard512(entry: PumpHistoryEntry): RecordDecodeStatus {
|
||||||
|
val body = entry.body
|
||||||
|
val dto = BolusWizardDTO()
|
||||||
|
val bolusStrokes = 10.0f
|
||||||
|
dto.bloodGlucose = (body.get(1) and 0x03).toInt() shl 8 or entry.head.get(0).toInt()
|
||||||
|
dto.carbs = body.get(1).toInt() and 0xC shl 6 or body.get(0).toInt() // (int)body[0];
|
||||||
|
dto.carbRatio = body.get(2).toFloat()
|
||||||
|
dto.insulinSensitivity = body.get(3).toFloat()
|
||||||
|
dto.bgTargetLow = body.get(4).toInt()
|
||||||
|
dto.foodEstimate = body.get(6) / 10.0f
|
||||||
|
dto.correctionEstimate = (body.get(7) + (body.get(5) and 0x0F)) / bolusStrokes
|
||||||
|
dto.unabsorbedInsulin = body.get(9) / bolusStrokes
|
||||||
|
dto.bolusTotal = body.get(11) / bolusStrokes
|
||||||
|
dto.bgTargetHigh = dto.bgTargetLow
|
||||||
|
if (dto.bloodGlucose < 0) {
|
||||||
|
dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.toByte())
|
||||||
|
}
|
||||||
|
dto.atechDateTime = entry.atechDateTime
|
||||||
|
entry.addDecodedData("Object", dto)
|
||||||
|
entry.displayableValue = dto.displayableValue
|
||||||
|
return RecordDecodeStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeLowReservoir(entry: PumpHistoryEntry) {
|
||||||
|
val amount = getUnsignedInt(entry.head.get(0)) * 1.0f / 10.0f * 2
|
||||||
|
entry.displayableValue = getFormattedValue(amount, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodePrime(entry: PumpHistoryEntry) {
|
||||||
|
val amount = ByteUtil.toInt(entry.head.get(2), entry.head.get(3)) / 10.0f
|
||||||
|
val fixed = ByteUtil.toInt(entry.head.get(0), entry.head.get(1)) / 10.0f
|
||||||
|
|
||||||
|
// amount = (double)(asUINT8(data[4]) << 2) / 40.0;
|
||||||
|
// programmedAmount = (double)(asUINT8(data[2]) << 2) / 40.0;
|
||||||
|
// primeType = programmedAmount == 0 ? "manual" : "fixed";
|
||||||
|
entry.addDecodedData("Amount", amount)
|
||||||
|
entry.addDecodedData("FixedAmount", fixed)
|
||||||
|
entry.displayableValue = ("Amount=" + getFormattedValue(amount, 2) + ", Fixed Amount="
|
||||||
|
+ getFormattedValue(fixed, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeChangeTempBasalType(entry: PumpHistoryEntry) {
|
||||||
|
entry.addDecodedData("isPercent", ByteUtil.asUINT8(entry.getRawDataByIndex(0)) == 1) // index moved from 1 -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBgReceived(entry: PumpHistoryEntry) {
|
||||||
|
entry.addDecodedData("amount", (ByteUtil.asUINT8(entry.getRawDataByIndex(0)) shl 3) + (ByteUtil.asUINT8(entry.getRawDataByIndex(3)) shr 5))
|
||||||
|
entry.addDecodedData("meter", ByteUtil.substring(entry.rawData, 6, 3)) // index moved from 1 -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeCalBGForPH(entry: PumpHistoryEntry) {
|
||||||
|
entry.addDecodedData("amount", (ByteUtil.asUINT8(entry.getRawDataByIndex(5)) and 0x80 shl 1) + ByteUtil.asUINT8(entry.getRawDataByIndex(0))) // index moved from 1 -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// private fun decodeNoDeliveryAlarm(entry: PumpHistoryEntry) {
|
||||||
|
// //rawtype = asUINT8(data[1]);
|
||||||
|
// // not sure if this is actually NoDelivery Alarm?
|
||||||
|
// }
|
||||||
|
|
||||||
|
override fun postProcess() {}
|
||||||
|
|
||||||
|
override fun runPostDecodeTasks() {
|
||||||
|
showStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeBolus(entry: PumpHistoryEntry) {
|
||||||
|
val bolus: BolusDTO?
|
||||||
|
val data = entry.head
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) {
|
||||||
|
bolus = BolusDTO(atechDateTime = entry.atechDateTime,
|
||||||
|
requestedAmount = ByteUtil.toInt(data.get(0), data.get(1)) / 40.0,
|
||||||
|
deliveredAmount = ByteUtil.toInt(data.get(2), data.get(3)) / 40.0,
|
||||||
|
duration = data.get(6) * 30)
|
||||||
|
bolus.insulinOnBoard = ByteUtil.toInt(data.get(4), data.get(5)) / 40.0
|
||||||
|
} else {
|
||||||
|
bolus = BolusDTO(atechDateTime = entry.atechDateTime,
|
||||||
|
requestedAmount = ByteUtil.asUINT8(data.get(0)) / 10.0,
|
||||||
|
deliveredAmount = ByteUtil.asUINT8(data.get(1)) / 10.0,
|
||||||
|
duration = ByteUtil.asUINT8(data.get(2)) * 30)
|
||||||
|
}
|
||||||
|
bolus.bolusType = if (bolus.duration > 0) PumpBolusType.Extended else PumpBolusType.Normal
|
||||||
|
entry.addDecodedData("Object", bolus)
|
||||||
|
entry.displayableValue = bolus.displayableValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeTempBasal(tbrPreviousRecord: PumpHistoryEntry, entry: PumpHistoryEntry) {
|
||||||
|
var tbrRate: PumpHistoryEntry? = null
|
||||||
|
var tbrDuration: PumpHistoryEntry? = null
|
||||||
|
if (entry.entryType === PumpHistoryEntryType.TempBasalRate) {
|
||||||
|
tbrRate = entry
|
||||||
|
} else {
|
||||||
|
tbrDuration = entry
|
||||||
|
}
|
||||||
|
if (tbrRate != null) {
|
||||||
|
tbrDuration = tbrPreviousRecord
|
||||||
|
} else {
|
||||||
|
tbrRate = tbrPreviousRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
val tbr = TempBasalPair(
|
||||||
|
tbrRate.head.get(0),
|
||||||
|
tbrRate.body.get(0),
|
||||||
|
tbrDuration!!.head.get(0).toInt(),
|
||||||
|
ByteUtil.asUINT8(tbrRate.datetime.get(4)) shr 3 == 0)
|
||||||
|
|
||||||
|
entry.addDecodedData("Object", tbr)
|
||||||
|
entry.displayableValue = tbr.description
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDateTime(entry: PumpHistoryEntry) {
|
||||||
|
if (entry.datetime.size == 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "DateTime not set.")
|
||||||
|
}
|
||||||
|
val dt = entry.datetime
|
||||||
|
if (entry.dateTimeLength == 5) {
|
||||||
|
val seconds: Int = (dt.get(0) and 0x3F.toByte()).toInt()
|
||||||
|
val minutes: Int = (dt.get(1) and 0x3F.toByte()).toInt()
|
||||||
|
val hour: Int = (dt.get(2) and 0x1F).toInt()
|
||||||
|
val month: Int = (dt.get(0).toInt() shr 4 and 0x0c) + (dt.get(1).toInt() shr 6 and 0x03)
|
||||||
|
// ((dt[0] & 0xC0) >> 6) | ((dt[1] & 0xC0) >> 4);
|
||||||
|
val dayOfMonth: Int = (dt.get(3) and 0x1F).toInt()
|
||||||
|
val year = fix2DigitYear((dt.get(4) and 0x3F.toByte()).toInt()) // Assuming this is correct, need to verify. Otherwise this will be
|
||||||
|
// a problem in 2016.
|
||||||
|
entry.atechDateTime = DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds)
|
||||||
|
} else if (entry.dateTimeLength == 2) {
|
||||||
|
//val low = ByteUtil.asUINT8(dt.get(0)) and 0x1F
|
||||||
|
val mhigh = ByteUtil.asUINT8(dt.get(0)) and 0xE0 shr 4
|
||||||
|
val mlow = ByteUtil.asUINT8(dt.get(1)) and 0x80 shr 7
|
||||||
|
val month = mhigh + mlow
|
||||||
|
// int dayOfMonth = low + 1;
|
||||||
|
val dayOfMonth: Int = (dt.get(0) and 0x1F).toInt()
|
||||||
|
val year = 2000 + (ByteUtil.asUINT8(dt.get(1)) and 0x7F)
|
||||||
|
var hour = 0
|
||||||
|
var minutes = 0
|
||||||
|
var seconds = 0
|
||||||
|
|
||||||
|
//LOG.debug("DT: {} {} {}", year, month, dayOfMonth);
|
||||||
|
if (dayOfMonth == 32) {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "Entry: Day 32 %s = [%s] %s", entry.entryType.name,
|
||||||
|
ByteUtil.getHex(entry.rawData), entry))
|
||||||
|
}
|
||||||
|
if (isEndResults(entry.entryType)) {
|
||||||
|
hour = 23
|
||||||
|
minutes = 59
|
||||||
|
seconds = 59
|
||||||
|
}
|
||||||
|
entry.atechDateTime = DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds)
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Unknown datetime format: " + entry.dateTimeLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isEndResults(entryType: PumpHistoryEntryType?): Boolean {
|
||||||
|
return entryType === PumpHistoryEntryType.EndResultTotals ||
|
||||||
|
entryType === PumpHistoryEntryType.DailyTotals515 ||
|
||||||
|
entryType === PumpHistoryEntryType.DailyTotals522 ||
|
||||||
|
entryType === PumpHistoryEntryType.DailyTotals523
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fix2DigitYear(year: Int): Int {
|
||||||
|
var yearInternal = year
|
||||||
|
yearInternal += if (yearInternal > 90) {
|
||||||
|
1900
|
||||||
|
} else {
|
||||||
|
2000
|
||||||
|
}
|
||||||
|
return yearInternal
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun getFormattedValue(value: Float, decimals: Int): String {
|
||||||
|
return String.format(Locale.ENGLISH, "%." + decimals + "f", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,174 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PumpHistoryEntry extends MedtronicHistoryEntry {
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
private PumpHistoryEntryType entryType;
|
|
||||||
private Integer opCode; // this is set only when we have unknown entry...
|
|
||||||
private int offset;
|
|
||||||
private String displayableValue = "";
|
|
||||||
|
|
||||||
|
|
||||||
public PumpHistoryEntryType getEntryType() {
|
|
||||||
return entryType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setEntryType(MedtronicDeviceType medtronicDeviceType, PumpHistoryEntryType entryType) {
|
|
||||||
this.entryType = entryType;
|
|
||||||
|
|
||||||
this.sizes[0] = entryType.getHeadLength(medtronicDeviceType);
|
|
||||||
this.sizes[1] = entryType.getDateLength();
|
|
||||||
this.sizes[2] = entryType.getBodyLength(medtronicDeviceType);
|
|
||||||
|
|
||||||
if (this.entryType != null && this.atechDateTime != null)
|
|
||||||
setPumpId();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setPumpId() {
|
|
||||||
this.pumpId = this.entryType.getCode() + (this.atechDateTime * 1000L);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOpCode() {
|
|
||||||
if (opCode == null)
|
|
||||||
return entryType.getOpCode();
|
|
||||||
else
|
|
||||||
return opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setOpCode(Integer opCode) {
|
|
||||||
this.opCode = opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getToStringStart() {
|
|
||||||
return "PumpHistoryEntry [type=" + StringUtil.getStringInLength(entryType.name(), 20) + " ["
|
|
||||||
+ StringUtil.getStringInLength("" + getOpCode(), 3) + ", 0x"
|
|
||||||
+ ByteUtil.shortHexString((byte) getOpCode()) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return super.toString();
|
|
||||||
// Object object = this.getDecodedDataEntry("Object");
|
|
||||||
//
|
|
||||||
// if (object == null) {
|
|
||||||
// return super.toString();
|
|
||||||
// } else {
|
|
||||||
// return super.toString() + "PumpHistoryEntry [type=" + StringUtil.getStringInLength(entryType.name(), 20) + ", DT: " + DT + ", Object=" + object.toString() + "]";
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getOffset() {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setOffset(int offset) {
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEntryTypeName() {
|
|
||||||
return this.entryType.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDateLength() {
|
|
||||||
return this.entryType.getDateLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (!(o instanceof PumpHistoryEntry))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
PumpHistoryEntry that = (PumpHistoryEntry) o;
|
|
||||||
|
|
||||||
return entryType == that.entryType && //
|
|
||||||
this.atechDateTime == that.atechDateTime; // && //
|
|
||||||
// Objects.equals(this.decodedData, that.decodedData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(entryType, opCode, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// public boolean isAfter(LocalDateTime dateTimeIn) {
|
|
||||||
// // LOG.debug("Entry: " + this.dateTime);
|
|
||||||
// // LOG.debug("Datetime: " + dateTimeIn);
|
|
||||||
// // LOG.debug("Item after: " + this.dateTime.isAfter(dateTimeIn));
|
|
||||||
// return this.dateTime.isAfter(dateTimeIn);
|
|
||||||
// }
|
|
||||||
|
|
||||||
public boolean isAfter(long atechDateTime) {
|
|
||||||
if (this.atechDateTime == null) {
|
|
||||||
LOG.error("Date is null. Show object: " + toString());
|
|
||||||
return false; // FIXME shouldn't happen
|
|
||||||
}
|
|
||||||
|
|
||||||
return atechDateTime < this.atechDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDisplayableValue(String displayableValue) {
|
|
||||||
this.displayableValue = displayableValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDisplayableValue() {
|
|
||||||
return displayableValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Comparator implements java.util.Comparator<PumpHistoryEntry> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(PumpHistoryEntry o1, PumpHistoryEntry o2) {
|
|
||||||
int data = (int) (o2.atechDateTime - o1.atechDateTime);
|
|
||||||
|
|
||||||
if (data != 0)
|
|
||||||
return data;
|
|
||||||
|
|
||||||
return o2.getEntryType().getCode() - o1.getEntryType().getCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Long getPumpId() {
|
|
||||||
setPumpId();
|
|
||||||
|
|
||||||
return pumpId;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
class PumpHistoryEntry : MedtronicHistoryEntry() {
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var entryType: PumpHistoryEntryType = PumpHistoryEntryType.None
|
||||||
|
private set
|
||||||
|
|
||||||
|
override var opCode: Byte? = null
|
||||||
|
// this is set only when we have unknown entry...
|
||||||
|
get() = if (field == null) entryType.code else field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 0
|
||||||
|
var displayableValue = ""
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEntryType(medtronicDeviceType: MedtronicDeviceType, entryType: PumpHistoryEntryType, opCode: Byte? = null) {
|
||||||
|
this.entryType = entryType
|
||||||
|
sizes[0] = entryType.getHeadLength(medtronicDeviceType)
|
||||||
|
sizes[1] = entryType.dateLength
|
||||||
|
sizes[2] = entryType.getBodyLength(medtronicDeviceType)
|
||||||
|
if (isEntryTypeSet() && atechDateTime != 0L) pumpId = generatePumpId()
|
||||||
|
this.opCode = opCode
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun generatePumpId(): Long {
|
||||||
|
return entryType.code + atechDateTime * 1000L
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isEntryTypeSet(): Boolean {
|
||||||
|
return this.entryType != PumpHistoryEntryType.None
|
||||||
|
}
|
||||||
|
|
||||||
|
override val toStringStart: String
|
||||||
|
get() = ("PumpHistoryEntry [type=" + StringUtil.getStringInLength(entryType.name, 20) + " ["
|
||||||
|
+ StringUtil.getStringInLength("" + opCode, 3) + ", 0x"
|
||||||
|
+ ByteUtil.shortHexString(opCode!!) + "]")
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return super.toString()
|
||||||
|
// Object object = this.getDecodedDataEntry("Object");
|
||||||
|
//
|
||||||
|
// if (object == null) {
|
||||||
|
// return super.toString();
|
||||||
|
// } else {
|
||||||
|
// return super.toString() + "PumpHistoryEntry [type=" + StringUtil.getStringInLength(entryType.name(), 20) + ", DT: " + DT + ", Object=" + object.toString() + "]";
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val entryTypeName: String
|
||||||
|
get() = entryType.name
|
||||||
|
|
||||||
|
override val dateLength: Int
|
||||||
|
get() = entryType.dateLength
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is PumpHistoryEntry) return false
|
||||||
|
val that = other //as PumpHistoryEntry
|
||||||
|
return this.pumpId == that.pumpId
|
||||||
|
// return entryType == that.entryType && //
|
||||||
|
// atechDateTime === that.atechDateTime // && //
|
||||||
|
// Objects.equals(this.decodedData, that.decodedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return Objects.hash(entryType, opCode, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// public boolean isAfter(LocalDateTime dateTimeIn) {
|
||||||
|
// // LOG.debug("Entry: " + this.dateTime);
|
||||||
|
// // LOG.debug("Datetime: " + dateTimeIn);
|
||||||
|
// // LOG.debug("Item after: " + this.dateTime.isAfter(dateTimeIn));
|
||||||
|
// return this.dateTime.isAfter(dateTimeIn);
|
||||||
|
// }
|
||||||
|
fun isAfter(atechDateTime: Long): Boolean {
|
||||||
|
if (this.atechDateTime == 0L) {
|
||||||
|
// Log.e("PumpHistoryEntry", "Date is null. Show object: " + toString())
|
||||||
|
return false // FIXME shouldn't happen
|
||||||
|
}
|
||||||
|
return atechDateTime < this.atechDateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
class Comparator : java.util.Comparator<PumpHistoryEntry> {
|
||||||
|
|
||||||
|
override fun compare(o1: PumpHistoryEntry, o2: PumpHistoryEntry): Int {
|
||||||
|
val data = (o2.atechDateTime - o1.atechDateTime).toInt()
|
||||||
|
return if (data != 0) data else o2.entryType.code - o1.entryType.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var pumpId: Long = 0L
|
||||||
|
get() {
|
||||||
|
if (field == 0L) {
|
||||||
|
field = generatePumpId()
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
set(pumpId) {
|
||||||
|
field = pumpId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasBolusChanged(entry: PumpHistoryEntry): Boolean {
|
||||||
|
if (entryType == PumpHistoryEntryType.Bolus) {
|
||||||
|
val thisOne: BolusDTO = this.decodedData["Object"] as BolusDTO
|
||||||
|
|
||||||
|
if (entry.entryType == PumpHistoryEntryType.Bolus) {
|
||||||
|
val otherOne: BolusDTO = entry.decodedData["Object"] as BolusDTO
|
||||||
|
return (thisOne.value.equals(otherOne.value))
|
||||||
|
} else
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,378 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
|
||||||
* management and modified/extended for AAPS.
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy.rozman@gmail.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum PumpHistoryEntryType // implements CodeEnum
|
|
||||||
{
|
|
||||||
// all commented out are probably not the real items
|
|
||||||
None(0, "None", PumpHistoryEntryGroup.Unknown, 1, 0, 0),
|
|
||||||
Bolus(0x01, "Bolus", PumpHistoryEntryGroup.Bolus, 4, 5, 0), // 523+[H=8] 9/13
|
|
||||||
Prime(0x03, "Prime", PumpHistoryEntryGroup.Prime, 5, 5, 0), //
|
|
||||||
// /**/EventUnknown_MM522_0x05((byte) 0x05, "Unknown Event 0x05", PumpHistoryEntryGroup.Unknown, 2, 5, 28), //
|
|
||||||
NoDeliveryAlarm(0x06, "No Delivery", PumpHistoryEntryGroup.Alarm, 4, 5, 0), //
|
|
||||||
EndResultTotals(0x07, "End Result Totals", PumpHistoryEntryGroup.Statistic, 5, 2, 0),
|
|
||||||
ChangeBasalProfile_OldProfile(0x08, "Change Basal Profile (Old)", PumpHistoryEntryGroup.Basal, 2, 5, 145),
|
|
||||||
ChangeBasalProfile_NewProfile(0x09, "Change Basal Profile (New)", PumpHistoryEntryGroup.Basal, 2, 5, 145), //
|
|
||||||
// /**/EventUnknown_MM512_0x10(0x10, "Unknown Event 0x10", PumpHistoryEntryGroup.Unknown), // 29, 5, 0
|
|
||||||
CalBGForPH(0x0a, "BG Capture", PumpHistoryEntryGroup.Glucose), //
|
|
||||||
SensorAlert(0x0b, "Sensor Alert", PumpHistoryEntryGroup.Alarm, 3, 5, 0), // Ian08
|
|
||||||
ClearAlarm(0x0c, "Clear Alarm", PumpHistoryEntryGroup.Alarm, 2, 5, 0), // 2,5,4
|
|
||||||
ChangeBasalPattern(0x14, "Change Basal Pattern", PumpHistoryEntryGroup.Basal), //
|
|
||||||
TempBasalDuration(0x16, "TBR Duration", PumpHistoryEntryGroup.Basal), //
|
|
||||||
ChangeTime(0x17, "Change Time", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
NewTimeSet(0x18, "New Time Set", PumpHistoryEntryGroup.Notification), //
|
|
||||||
LowBattery(0x19, "LowBattery", PumpHistoryEntryGroup.Notification), //
|
|
||||||
BatteryChange(0x1a, "Battery Change", PumpHistoryEntryGroup.Notification), //
|
|
||||||
SetAutoOff(0x1b, "Set Auto Off", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
SuspendPump(0x1e, "Suspend", PumpHistoryEntryGroup.Basal), //
|
|
||||||
ResumePump(0x1f, "Resume", PumpHistoryEntryGroup.Basal), //
|
|
||||||
SelfTest(0x20, "Self Test", PumpHistoryEntryGroup.Statistic), //
|
|
||||||
Rewind(0x21, "Rewind", PumpHistoryEntryGroup.Prime), //
|
|
||||||
ClearSettings(0x22, "Clear Settings", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeChildBlockEnable(0x23, "Change Child Block Enable", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeMaxBolus(0x24, "Change Max Bolus", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
// /**/EventUnknown_MM522_0x25(0x25, "Unknown Event 0x25", PumpHistoryEntryGroup.Unknown), // 8?
|
|
||||||
EnableDisableRemote(0x26, "Enable/Disable Remote", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // 2, 5, 14 V6:2,5,14
|
|
||||||
ChangeRemoteId(0x27, "Change Remote ID", PumpHistoryEntryGroup.Configuration), // ??
|
|
||||||
ChangeMaxBasal(0x2c, "Change Max Basal", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
BolusWizardEnabled(0x2d, "Bolus Wizard Enabled", PumpHistoryEntryGroup.Configuration), // V3 ?
|
|
||||||
/* TODO */EventUnknown_MM512_0x2e(0x2e, "Unknown Event 0x2e", PumpHistoryEntryGroup.Unknown, 2, 5, 100), //
|
|
||||||
BolusWizard512(0x2f, "Bolus Wizard (512)", PumpHistoryEntryGroup.Bolus, 2, 5, 12), //
|
|
||||||
UnabsorbedInsulin512(0x30, "Unabsorbed Insulin (512)", PumpHistoryEntryGroup.Statistic, 5, 0, 0), // FIXME
|
|
||||||
ChangeBGReminderOffset(0x31, "Change BG Reminder Offset", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeAlarmClockTime(0x32, "Change Alarm Clock Time", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
TempBasalRate(0x33, "TBR Rate", PumpHistoryEntryGroup.Basal, 2, 5, 1), //
|
|
||||||
LowReservoir(0x34, "Low Reservoir", PumpHistoryEntryGroup.Notification), //
|
|
||||||
ChangeAlarmClock(0x35, "Change Alarm Clock", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeMeterId(0x36, "Change Meter ID", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
// /**/EventUnknown_MM512_0x37(0x37, "Unknown Event 0x37", PumpHistoryEntryGroup.Unknown), // V:MM512
|
|
||||||
// /**/EventUnknown_MM512_0x38(0x38, "Unknown Event 0x38", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
BGReceived512(0x39, "BG Received (512)", PumpHistoryEntryGroup.Glucose, 2, 5, 3), //
|
|
||||||
/* TODO */ConfirmInsulinChange(0x3a, "Confirm Insulin Change", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
SensorStatus(0x3b, "Sensor Status", PumpHistoryEntryGroup.Glucose), //
|
|
||||||
ChangeParadigmID(0x3c, "Change Paradigm ID", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // V3 ? V6: 2,5,14 ?? is it this length or just 7
|
|
||||||
// EventUnknown_MM512_0x3D(0x3d, "Unknown Event 0x3D", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
// EventUnknown_MM512_0x3E(0x3e, "Unknown Event 0x3E", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
BGReceived(0x3f, "BG Received", PumpHistoryEntryGroup.Glucose, 2, 5, 3), // Ian3F
|
|
||||||
JournalEntryMealMarker(0x40, "Meal Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 2), // is size just 7??? V6
|
|
||||||
JournalEntryExerciseMarker(0x41, "Exercise Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // ??
|
|
||||||
JournalEntryInsulinMarker(0x42, "Insulin Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 0), // V6 = body(0)/was=1
|
|
||||||
JournalEntryOtherMarker(0x43, "Other Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // V6 = body(1)/was=0
|
|
||||||
EnableSensorAutoCal(0x44, "Enable Sensor AutoCal", PumpHistoryEntryGroup.Glucose), //
|
|
||||||
// /**/EventUnknown_MM522_0x45(0x45, "Unknown Event 0x45", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x46(0x46, "Unknown Event 0x46", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x47(0x47, "Unknown Event 0x47", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x48(0x48, "Unknown Event 0x48", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x49(0x49, "Unknown Event 0x49", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x4a(0x4a, "Unknown Event 0x4a", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x4b(0x4b, "Unknown Event 0x4b", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_MM522_0x4c(0x4c, "Unknown Event 0x4c", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
// /**/EventUnknown_0x4d(0x4d, "Unknown Event 0x4d", PumpHistoryEntryGroup.Unknown), // V5: 512: 7, 522: 8 ????NS
|
|
||||||
// /**/EventUnknown_MM512_0x4e(0x4e, "Unknown Event 0x4e", PumpHistoryEntryGroup.Unknown), // /**/
|
|
||||||
BolusWizardSetup512(0x4f, "Bolus Wizard Setup (512)", PumpHistoryEntryGroup.Configuration, 2, 5, 32), //
|
|
||||||
ChangeSensorSetup2(0x50, "Sensor Setup2", PumpHistoryEntryGroup.Configuration, 2, 5, 30), // Ian50
|
|
||||||
/* TODO */Sensor_0x51(0x51, "Unknown Event 0x51", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
/* TODO */Sensor_0x52(0x52, "Unknown Event 0x52", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
ChangeSensorAlarmSilenceConfig(0x53, "Sensor Alarm Silence Config", PumpHistoryEntryGroup.Configuration, 2, 5, 1), // 8
|
|
||||||
/* TODO */Sensor_0x54(0x54, "Unknown Event 0x54", PumpHistoryEntryGroup.Unknown), // Ian54
|
|
||||||
/* TODO */Sensor_0x55(0x55, "Unknown Event 0x55", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
ChangeSensorRateOfChangeAlertSetup(0x56, "Sensor Rate Of Change Alert Setup", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12
|
|
||||||
ChangeBolusScrollStepSize(0x57, "Change Bolus Scroll Step Size", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
BolusWizardSetup(0x5a, "Bolus Wizard Setup (522)", PumpHistoryEntryGroup.Configuration, 2, 5, 117),
|
|
||||||
// V2: 522+[B=143]; V6: 124, v6: 137, v7: 117/137 [523]
|
|
||||||
BolusWizard(0x5b, "Bolus Wizard Estimate", PumpHistoryEntryGroup.Configuration, 2, 5, 13), // 15 //
|
|
||||||
UnabsorbedInsulin(0x5c, "Unabsorbed Insulin", PumpHistoryEntryGroup.Statistic, 5, 0, 0), // head[1] -> body
|
|
||||||
SaveSettings(0x5d, "Save Settings", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeVariableBolus(0x5e, "Change Variable Bolus", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeAudioBolus(0x5f, "Easy Bolus Enabled", PumpHistoryEntryGroup.Configuration), // V3 ?
|
|
||||||
ChangeBGReminderEnable(0x60, "BG Reminder Enable", PumpHistoryEntryGroup.Configuration), // questionable60
|
|
||||||
ChangeAlarmClockEnable(0x61, "Alarm Clock Enable", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeTempBasalType((byte) 0x62, "Change Basal Type", PumpHistoryEntryGroup.Configuration), // ChangeTempBasalTypePumpEvent
|
|
||||||
ChangeAlarmNotifyMode(0x63, "Change Alarm Notify Mode", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeTimeFormat(0x64, "Change Time Format", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeReservoirWarningTime((byte) 0x65, "Change Reservoir Warning Time", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeBolusReminderEnable(0x66, "Change Bolus Reminder Enable", PumpHistoryEntryGroup.Configuration), // 9 -> 7
|
|
||||||
SetBolusReminderTime((byte) 0x67, "Change Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9
|
|
||||||
DeleteBolusReminderTime((byte) 0x68, "Delete Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9
|
|
||||||
BolusReminder(0x69, "Bolus Reminder", PumpHistoryEntryGroup.Configuration, 2, 5, 0), // Ian69
|
|
||||||
DeleteAlarmClockTime(0x6a, "Delete Alarm Clock Time", PumpHistoryEntryGroup.Configuration, 2, 5, 7), // 14
|
|
||||||
DailyTotals515(0x6c, "Daily Totals (515)", PumpHistoryEntryGroup.Statistic, 1, 2, 35), // v4: 0,0,36. v5: 1,2,33
|
|
||||||
DailyTotals522(0x6d, "Daily Totals (522)", PumpHistoryEntryGroup.Statistic, 1, 2, 41), //
|
|
||||||
DailyTotals523(0x6e, "Daily Totals (523)", PumpHistoryEntryGroup.Statistic, 1, 2, 49), // 1102014-03-17T00:00:00
|
|
||||||
ChangeCarbUnits((byte) 0x6f, "Change Carb Units", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
// /**/EventUnknown_MM522_0x70((byte) 0x70, "Unknown Event 0x70", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
|
||||||
BasalProfileStart(0x7b, "Basal Profile Start", PumpHistoryEntryGroup.Basal, 2, 5, 3), // // 722
|
|
||||||
ChangeWatchdogEnable((byte) 0x7c, "Change Watchdog Enable", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
ChangeOtherDeviceID((byte) 0x7d, "Change Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 30), //
|
|
||||||
ChangeWatchdogMarriageProfile(0x81, "Change Watchdog Marriage Profile", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12
|
|
||||||
DeleteOtherDeviceID(0x82, "Delete Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 5), //
|
|
||||||
ChangeCaptureEventEnable(0x83, "Change Capture Event Enable", PumpHistoryEntryGroup.Configuration), //
|
|
||||||
// /**/EventUnknown_MM512_0x88(0x88, "Unknown Event 0x88", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
// /**/EventUnknown_MM512_0x94(0x94, "Unknown Event 0x94", PumpHistoryEntryGroup.Unknown), //
|
|
||||||
/**/ IanA8(0xA8, "Ian A8 (Unknown)", PumpHistoryEntryGroup.Unknown, 10, 5, 0), //
|
|
||||||
// /**/EventUnknown_MM522_0xE8(0xe8, "Unknown Event 0xE8", PumpHistoryEntryGroup.Unknown, 2, 5, 25), //
|
|
||||||
// F0 - F3 are not in go code, but they are in GGC one and thet are used
|
|
||||||
ReadOtherDevicesIDs(0xf0, "Read Other Devices IDs", PumpHistoryEntryGroup.Configuration), // ?
|
|
||||||
ReadCaptureEventEnabled(0xf1, "Read Capture Event Enabled", PumpHistoryEntryGroup.Configuration), // ?
|
|
||||||
ChangeCaptureEventEnable2(0xf2, "Change Capture Event Enable2", PumpHistoryEntryGroup.Configuration), // ?
|
|
||||||
ReadOtherDevicesStatus(0xf3, "Read Other Devices Status", PumpHistoryEntryGroup.Configuration), // ?
|
|
||||||
|
|
||||||
TempBasalCombined(0xfe, "TBR", PumpHistoryEntryGroup.Basal), //
|
|
||||||
UnknownBasePacket(0xff, "Unknown Base Packet", PumpHistoryEntryGroup.Unknown);
|
|
||||||
|
|
||||||
private static final Map<Integer, PumpHistoryEntryType> opCodeMap = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (PumpHistoryEntryType type : values()) {
|
|
||||||
opCodeMap.put(type.opCode, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSpecialRulesForEntryTypes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int opCode;
|
|
||||||
private final String description;
|
|
||||||
private final int headLength;
|
|
||||||
private final int dateLength;
|
|
||||||
// private MinimedDeviceType deviceType;
|
|
||||||
private final int bodyLength;
|
|
||||||
private final int totalLength;
|
|
||||||
// special rules need to be put in list from highest to lowest (e.g.:
|
|
||||||
// 523andHigher=12, 515andHigher=10 and default (set in cnstr) would be 8)
|
|
||||||
private List<SpecialRule> specialRulesHead;
|
|
||||||
private List<SpecialRule> specialRulesBody;
|
|
||||||
private boolean hasSpecialRules = false;
|
|
||||||
private final PumpHistoryEntryGroup group;
|
|
||||||
|
|
||||||
|
|
||||||
PumpHistoryEntryType(int opCode, String name, PumpHistoryEntryGroup group) {
|
|
||||||
this(opCode, name, group, 2, 5, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PumpHistoryEntryType(int opCode, PumpHistoryEntryGroup group) {
|
|
||||||
this(opCode, null, group, 2, 5, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PumpHistoryEntryType(int opCode, PumpHistoryEntryGroup group, int head, int date, int body) {
|
|
||||||
this(opCode, null, group, head, date, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
PumpHistoryEntryType(int opCode, String name, PumpHistoryEntryGroup group, int head, int date, int body) {
|
|
||||||
this.opCode = (byte) opCode;
|
|
||||||
this.description = name;
|
|
||||||
this.headLength = head;
|
|
||||||
this.dateLength = date;
|
|
||||||
this.bodyLength = body;
|
|
||||||
this.totalLength = (head + date + body);
|
|
||||||
this.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void setSpecialRulesForEntryTypes() {
|
|
||||||
EndResultTotals.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 3));
|
|
||||||
Bolus.addSpecialRuleHead(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 8));
|
|
||||||
BolusWizardSetup.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 137));
|
|
||||||
BolusWizard.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 15));
|
|
||||||
BolusReminder.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 2));
|
|
||||||
ChangeSensorSetup2.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 34));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static PumpHistoryEntryType getByCode(int opCode) {
|
|
||||||
if (opCodeMap.containsKey(opCode)) {
|
|
||||||
return opCodeMap.get(opCode);
|
|
||||||
} else {
|
|
||||||
return PumpHistoryEntryType.UnknownBasePacket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// private PumpHistoryEntryType(int opCode, String name, int head, int date,
|
|
||||||
// int body)
|
|
||||||
// {
|
|
||||||
// this.opCode = (byte) opCode;
|
|
||||||
// this.description = name;
|
|
||||||
// this.headLength = head;
|
|
||||||
// this.dateLength = date;
|
|
||||||
// this.bodyLength = body;
|
|
||||||
// this.totalLength = (head + date + body);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
public static boolean isAAPSRelevantEntry(PumpHistoryEntryType entryType) {
|
|
||||||
return (entryType == PumpHistoryEntryType.Bolus || // Treatments
|
|
||||||
entryType == PumpHistoryEntryType.TempBasalRate || //
|
|
||||||
entryType == PumpHistoryEntryType.TempBasalDuration || //
|
|
||||||
|
|
||||||
entryType == PumpHistoryEntryType.Prime || // Pump Status Change
|
|
||||||
entryType == PumpHistoryEntryType.SuspendPump || //
|
|
||||||
entryType == PumpHistoryEntryType.ResumePump || //
|
|
||||||
entryType == PumpHistoryEntryType.Rewind || //
|
|
||||||
entryType == PumpHistoryEntryType.NoDeliveryAlarm || // no delivery
|
|
||||||
entryType == PumpHistoryEntryType.BasalProfileStart || //
|
|
||||||
|
|
||||||
entryType == PumpHistoryEntryType.ChangeTime || // Time Change
|
|
||||||
entryType == PumpHistoryEntryType.NewTimeSet || //
|
|
||||||
|
|
||||||
entryType == PumpHistoryEntryType.ChangeBasalPattern || // Configuration
|
|
||||||
entryType == PumpHistoryEntryType.ClearSettings || //
|
|
||||||
entryType == PumpHistoryEntryType.SaveSettings || //
|
|
||||||
entryType == PumpHistoryEntryType.ChangeMaxBolus || //
|
|
||||||
entryType == PumpHistoryEntryType.ChangeMaxBasal || //
|
|
||||||
entryType == PumpHistoryEntryType.ChangeTempBasalType || //
|
|
||||||
|
|
||||||
entryType == PumpHistoryEntryType.ChangeBasalProfile_NewProfile || // Basal profile
|
|
||||||
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals515 || // Daily Totals
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals522 || //
|
|
||||||
entryType == PumpHistoryEntryType.DailyTotals523 || //
|
|
||||||
entryType == PumpHistoryEntryType.EndResultTotals);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean isRelevantEntry() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getCode() {
|
|
||||||
return this.opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getTotalLength(MedtronicDeviceType medtronicDeviceType) {
|
|
||||||
if (hasSpecialRules()) {
|
|
||||||
return getHeadLength(medtronicDeviceType) + getBodyLength(medtronicDeviceType) + getDateLength();
|
|
||||||
} else {
|
|
||||||
return totalLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean hasSpecialRules() {
|
|
||||||
return hasSpecialRules;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void addSpecialRuleHead(SpecialRule rule) {
|
|
||||||
if (isEmpty(specialRulesHead)) {
|
|
||||||
specialRulesHead = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
specialRulesHead.add(rule);
|
|
||||||
hasSpecialRules = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void addSpecialRuleBody(SpecialRule rule) {
|
|
||||||
if (isEmpty(specialRulesBody)) {
|
|
||||||
specialRulesBody = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
specialRulesBody.add(rule);
|
|
||||||
hasSpecialRules = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getOpCode() {
|
|
||||||
return opCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return this.description == null ? name() : this.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getHeadLength(MedtronicDeviceType medtronicDeviceType) {
|
|
||||||
if (hasSpecialRules) {
|
|
||||||
if (isNotEmpty(specialRulesHead)) {
|
|
||||||
return determineSizeByRule(medtronicDeviceType, headLength, specialRulesHead);
|
|
||||||
} else {
|
|
||||||
return headLength;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return headLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getDateLength() {
|
|
||||||
return dateLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getBodyLength(MedtronicDeviceType medtronicDeviceType) {
|
|
||||||
if (hasSpecialRules) {
|
|
||||||
if (isNotEmpty(specialRulesBody)) {
|
|
||||||
return determineSizeByRule(medtronicDeviceType, bodyLength, specialRulesBody);
|
|
||||||
} else {
|
|
||||||
return bodyLength;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return bodyLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isNotEmpty(List list) {
|
|
||||||
return list != null && !list.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean isEmpty(List list) {
|
|
||||||
return list == null || list.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// byte[] dh = { 2, 3 };
|
|
||||||
|
|
||||||
private int determineSizeByRule(MedtronicDeviceType medtronicDeviceType, int defaultValue, List<SpecialRule> rules) {
|
|
||||||
int size = defaultValue;
|
|
||||||
|
|
||||||
for (SpecialRule rule : rules) {
|
|
||||||
if (MedtronicDeviceType.isSameDevice(medtronicDeviceType, rule.deviceType)) {
|
|
||||||
size = rule.size;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpHistoryEntryGroup getGroup() {
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SpecialRule {
|
|
||||||
|
|
||||||
MedtronicDeviceType deviceType;
|
|
||||||
int size;
|
|
||||||
|
|
||||||
|
|
||||||
SpecialRule(MedtronicDeviceType deviceType, int size) {
|
|
||||||
this.deviceType = deviceType;
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
|
||||||
|
* management and modified/extended for AAPS.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy.rozman@gmail.com}
|
||||||
|
*/
|
||||||
|
enum class PumpHistoryEntryType // implements CodeEnum
|
||||||
|
constructor(var code: Byte,
|
||||||
|
var description: String,
|
||||||
|
var group: PumpHistoryEntryGroup,
|
||||||
|
var headLength: Int = 2,
|
||||||
|
var dateLength: Int = 5,
|
||||||
|
var bodyLength: Int = 0) {
|
||||||
|
|
||||||
|
// all commented out are probably not the real items
|
||||||
|
None(0, "None", PumpHistoryEntryGroup.Unknown, 1, 0, 0),
|
||||||
|
Bolus(0x01, "Bolus", PumpHistoryEntryGroup.Bolus, 4, 5, 0), // 523+[H=8] 9/13
|
||||||
|
Prime(0x03, "Prime", PumpHistoryEntryGroup.Prime, 5, 5, 0), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM522_0x05((byte) 0x05, "Unknown Event 0x05", PumpHistoryEntryGroup.Unknown, 2, 5, 28), //
|
||||||
|
NoDeliveryAlarm(0x06, "No Delivery", PumpHistoryEntryGroup.Alarm, 4, 5, 0), //
|
||||||
|
EndResultTotals(0x07, "End Result Totals", PumpHistoryEntryGroup.Statistic, 5, 2, 0), ChangeBasalProfile_OldProfile(0x08, "Change Basal Profile (Old)", PumpHistoryEntryGroup.Basal, 2, 5, 145), ChangeBasalProfile_NewProfile(0x09, "Change Basal Profile (New)", PumpHistoryEntryGroup.Basal, 2, 5, 145), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM512_0x10(0x10, "Unknown Event 0x10", PumpHistoryEntryGroup.Unknown), // 29, 5, 0
|
||||||
|
CalBGForPH(0x0a, "BG Capture", PumpHistoryEntryGroup.Glucose), //
|
||||||
|
SensorAlert(0x0b, "Sensor Alert", PumpHistoryEntryGroup.Alarm, 3, 5, 0), // Ian08
|
||||||
|
ClearAlarm(0x0c, "Clear Alarm", PumpHistoryEntryGroup.Alarm, 2, 5, 0), // 2,5,4
|
||||||
|
ChangeBasalPattern(0x14, "Change Basal Pattern", PumpHistoryEntryGroup.Basal), //
|
||||||
|
TempBasalDuration(0x16, "TBR Duration", PumpHistoryEntryGroup.Basal), //
|
||||||
|
ChangeTime(0x17, "Change Time", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
NewTimeSet(0x18, "New Time Set", PumpHistoryEntryGroup.Notification), //
|
||||||
|
LowBattery(0x19, "LowBattery", PumpHistoryEntryGroup.Notification), //
|
||||||
|
BatteryChange(0x1a, "Battery Change", PumpHistoryEntryGroup.Notification), //
|
||||||
|
SetAutoOff(0x1b, "Set Auto Off", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
SuspendPump(0x1e, "Suspend", PumpHistoryEntryGroup.Basal), //
|
||||||
|
ResumePump(0x1f, "Resume", PumpHistoryEntryGroup.Basal), //
|
||||||
|
SelfTest(0x20, "Self Test", PumpHistoryEntryGroup.Statistic), //
|
||||||
|
Rewind(0x21, "Rewind", PumpHistoryEntryGroup.Prime), //
|
||||||
|
ClearSettings(0x22, "Clear Settings", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeChildBlockEnable(0x23, "Change Child Block Enable", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeMaxBolus(0x24, "Change Max Bolus", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM522_0x25(0x25, "Unknown Event 0x25", PumpHistoryEntryGroup.Unknown), // 8?
|
||||||
|
EnableDisableRemote(0x26, "Enable/Disable Remote", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // 2, 5, 14 V6:2,5,14
|
||||||
|
ChangeRemoteId(0x27, "Change Remote ID", PumpHistoryEntryGroup.Configuration), // ??
|
||||||
|
ChangeMaxBasal(0x2c, "Change Max Basal", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
BolusWizardEnabled(0x2d, "Bolus Wizard Enabled", PumpHistoryEntryGroup.Configuration), // V3 ?
|
||||||
|
/* TODO */ EventUnknown_MM512_0x2e(0x2e, "Unknown Event 0x2e", PumpHistoryEntryGroup.Unknown, 2, 5, 100), //
|
||||||
|
BolusWizard512(0x2f, "Bolus Wizard (512)", PumpHistoryEntryGroup.Bolus, 2, 5, 12), //
|
||||||
|
UnabsorbedInsulin512(0x30, "Unabsorbed Insulin (512)", PumpHistoryEntryGroup.Statistic, 5, 0, 0),
|
||||||
|
ChangeBGReminderOffset(0x31, "Change BG Reminder Offset", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeAlarmClockTime(0x32, "Change Alarm Clock Time", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
TempBasalRate(0x33, "TBR Rate", PumpHistoryEntryGroup.Basal, 2, 5, 1), //
|
||||||
|
LowReservoir(0x34, "Low Reservoir", PumpHistoryEntryGroup.Notification), //
|
||||||
|
ChangeAlarmClock(0x35, "Change Alarm Clock", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeMeterId(0x36, "Change Meter ID", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM512_0x37(0x37, "Unknown Event 0x37", PumpHistoryEntryGroup.Unknown), // V:MM512
|
||||||
|
// /**/EventUnknown_MM512_0x38(0x38, "Unknown Event 0x38", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
BGReceived512(0x39, "BG Received (512)", PumpHistoryEntryGroup.Glucose, 2, 5, 3), //
|
||||||
|
/* TODO */ ConfirmInsulinChange(0x3a, "Confirm Insulin Change", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
SensorStatus(0x3b, "Sensor Status", PumpHistoryEntryGroup.Glucose), //
|
||||||
|
ChangeParadigmID(0x3c, "Change Paradigm ID", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // V3 ? V6: 2,5,14 ?? is it this length or just 7
|
||||||
|
|
||||||
|
// EventUnknown_MM512_0x3D(0x3d, "Unknown Event 0x3D", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
// EventUnknown_MM512_0x3E(0x3e, "Unknown Event 0x3E", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
BGReceived(0x3f, "BG Received", PumpHistoryEntryGroup.Glucose, 2, 5, 3), // Ian3F
|
||||||
|
JournalEntryMealMarker(0x40, "Meal Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 2), // is size just 7??? V6
|
||||||
|
JournalEntryExerciseMarker(0x41, "Exercise Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // ??
|
||||||
|
JournalEntryInsulinMarker(0x42, "Insulin Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 0), // V6 = body(0)/was=1
|
||||||
|
JournalEntryOtherMarker(0x43, "Other Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // V6 = body(1) was=0
|
||||||
|
EnableSensorAutoCal(0x44, "Enable Sensor AutoCal", PumpHistoryEntryGroup.Glucose), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM522_0x45(0x45, "Unknown Event 0x45", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x46(0x46, "Unknown Event 0x46", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x47(0x47, "Unknown Event 0x47", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x48(0x48, "Unknown Event 0x48", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x49(0x49, "Unknown Event 0x49", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x4a(0x4a, "Unknown Event 0x4a", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x4b(0x4b, "Unknown Event 0x4b", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_MM522_0x4c(0x4c, "Unknown Event 0x4c", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
// /**/EventUnknown_0x4d(0x4d, "Unknown Event 0x4d", PumpHistoryEntryGroup.Unknown), // V5: 512: 7, 522: 8 ????NS
|
||||||
|
// /**/EventUnknown_MM512_0x4e(0x4e, "Unknown Event 0x4e", PumpHistoryEntryGroup.Unknown), // /**/
|
||||||
|
BolusWizardSetup512(0x4f, "Bolus Wizard Setup (512)", PumpHistoryEntryGroup.Configuration, 2, 5, 32), //
|
||||||
|
ChangeSensorSetup2(0x50, "Sensor Setup2", PumpHistoryEntryGroup.Configuration, 2, 5, 30), // Ian50
|
||||||
|
|
||||||
|
/* TODO */ Sensor_0x51(0x51, "Unknown Event 0x51", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
/* TODO */ Sensor_0x52(0x52, "Unknown Event 0x52", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
ChangeSensorAlarmSilenceConfig(0x53, "Sensor Alarm Silence Config", PumpHistoryEntryGroup.Configuration, 2, 5, 1), // 8
|
||||||
|
/* TODO */ Sensor_0x54(0x54, "Unknown Event 0x54", PumpHistoryEntryGroup.Unknown), // Ian54
|
||||||
|
/* TODO */ Sensor_0x55(0x55, "Unknown Event 0x55", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
ChangeSensorRateOfChangeAlertSetup(0x56, "Sensor Rate Of Change Alert Setup", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12
|
||||||
|
ChangeBolusScrollStepSize(0x57, "Change Bolus Scroll Step Size", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
BolusWizardSetup(0x5a, "Bolus Wizard Setup (522)", PumpHistoryEntryGroup.Configuration, 2, 5, 117), // V2: 522+[B=143]; V6: 124, v6: 137, v7: 117/137 [523]
|
||||||
|
BolusWizard(0x5b, "Bolus Wizard Estimate", PumpHistoryEntryGroup.Configuration, 2, 5, 13), // 15 //
|
||||||
|
UnabsorbedInsulin(0x5c, "Unabsorbed Insulin", PumpHistoryEntryGroup.Statistic, 5, 0, 0), // head[1] -> body
|
||||||
|
SaveSettings(0x5d, "Save Settings", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeVariableBolus(0x5e, "Change Variable Bolus", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeAudioBolus(0x5f, "Easy Bolus Enabled", PumpHistoryEntryGroup.Configuration), // V3 ?
|
||||||
|
ChangeBGReminderEnable(0x60, "BG Reminder Enable", PumpHistoryEntryGroup.Configuration), // questionable60
|
||||||
|
ChangeAlarmClockEnable(0x61, "Alarm Clock Enable", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeTempBasalType(0x62.toByte(), "Change Basal Type", PumpHistoryEntryGroup.Configuration), // ChangeTempBasalTypePumpEvent
|
||||||
|
ChangeAlarmNotifyMode(0x63, "Change Alarm Notify Mode", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeTimeFormat(0x64, "Change Time Format", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeReservoirWarningTime(0x65.toByte(), "Change Reservoir Warning Time", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeBolusReminderEnable(0x66, "Change Bolus Reminder Enable", PumpHistoryEntryGroup.Configuration), // 9
|
||||||
|
SetBolusReminderTime(0x67.toByte(), "Change Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9
|
||||||
|
DeleteBolusReminderTime(0x68.toByte(), "Delete Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9
|
||||||
|
BolusReminder(0x69, "Bolus Reminder", PumpHistoryEntryGroup.Configuration, 2, 5, 0), // Ian69
|
||||||
|
DeleteAlarmClockTime(0x6a, "Delete Alarm Clock Time", PumpHistoryEntryGroup.Configuration, 2, 5, 7), // 14
|
||||||
|
DailyTotals515(0x6c, "Daily Totals (515)", PumpHistoryEntryGroup.Statistic, 1, 2, 35), // v4: 0,0,36. v5: 1,2,33
|
||||||
|
DailyTotals522(0x6d, "Daily Totals (522)", PumpHistoryEntryGroup.Statistic, 1, 2, 41), //
|
||||||
|
DailyTotals523(0x6e, "Daily Totals (523)", PumpHistoryEntryGroup.Statistic, 1, 2, 49), // 1102014-03-17T00:00:00
|
||||||
|
ChangeCarbUnits(0x6f.toByte(), "Change Carb Units", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM522_0x70((byte) 0x70, "Unknown Event 0x70", PumpHistoryEntryGroup.Unknown, 2, 5, 1), //
|
||||||
|
BasalProfileStart(0x7b, "Basal Profile Start", PumpHistoryEntryGroup.Basal, 2, 5, 3), // // 722
|
||||||
|
ChangeWatchdogEnable(0x7c, "Change Watchdog Enable", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
ChangeOtherDeviceID(0x7d, "Change Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 30), //
|
||||||
|
ChangeWatchdogMarriageProfile(0x81.toByte(), "Change Watchdog Marriage Profile", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12
|
||||||
|
DeleteOtherDeviceID(0x82.toByte(), "Delete Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 5), //
|
||||||
|
ChangeCaptureEventEnable(0x83.toByte(), "Change Capture Event Enable", PumpHistoryEntryGroup.Configuration), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM512_0x88(0x88, "Unknown Event 0x88", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
// /**/EventUnknown_MM512_0x94(0x94, "Unknown Event 0x94", PumpHistoryEntryGroup.Unknown), //
|
||||||
|
/**/
|
||||||
|
IanA8(0xA8.toByte(), "Ian A8 (Unknown)", PumpHistoryEntryGroup.Unknown, 10, 5, 0), //
|
||||||
|
|
||||||
|
// /**/EventUnknown_MM522_0xE8(0xe8, "Unknown Event 0xE8", PumpHistoryEntryGroup.Unknown, 2, 5, 25), //
|
||||||
|
// F0 - F3 are not in go code, but they are in GGC one and thet are used
|
||||||
|
ReadOtherDevicesIDs(0xf0.toByte(), "Read Other Devices IDs", PumpHistoryEntryGroup.Configuration), // ?
|
||||||
|
ReadCaptureEventEnabled(0xf1.toByte(), "Read Capture Event Enabled", PumpHistoryEntryGroup.Configuration), // ?
|
||||||
|
ChangeCaptureEventEnable2(0xf2.toByte(), "Change Capture Event Enable2", PumpHistoryEntryGroup.Configuration), // ?
|
||||||
|
ReadOtherDevicesStatus(0xf3.toByte(), "Read Other Devices Status", PumpHistoryEntryGroup.Configuration), // ?
|
||||||
|
TempBasalCombined(0xfe.toByte(), "TBR", PumpHistoryEntryGroup.Basal), //
|
||||||
|
UnknownBasePacket(0xff.toByte(), "Unknown Base Packet", PumpHistoryEntryGroup.Unknown);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val opCodeMap: MutableMap<Byte, PumpHistoryEntryType?> = HashMap()
|
||||||
|
fun setSpecialRulesForEntryTypes() {
|
||||||
|
EndResultTotals.addSpecialRuleBody(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 3))
|
||||||
|
Bolus.addSpecialRuleHead(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 8))
|
||||||
|
BolusWizardSetup.addSpecialRuleBody(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 137))
|
||||||
|
BolusWizard.addSpecialRuleBody(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 15))
|
||||||
|
BolusReminder.addSpecialRuleBody(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 2))
|
||||||
|
ChangeSensorSetup2.addSpecialRuleBody(SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 34))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getByCode(opCode: Byte): PumpHistoryEntryType {
|
||||||
|
return if (opCodeMap.containsKey(opCode)) {
|
||||||
|
opCodeMap[opCode]!!
|
||||||
|
} else {
|
||||||
|
UnknownBasePacket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAAPSRelevantEntry(entryType: PumpHistoryEntryType): Boolean {
|
||||||
|
return entryType == Bolus || // Treatments
|
||||||
|
entryType == TempBasalRate || //
|
||||||
|
entryType == TempBasalDuration || //
|
||||||
|
entryType == Prime || // Pump Status Change
|
||||||
|
entryType == SuspendPump || //
|
||||||
|
entryType == ResumePump || //
|
||||||
|
entryType == Rewind || //
|
||||||
|
entryType == NoDeliveryAlarm || // no delivery
|
||||||
|
entryType == BasalProfileStart || //
|
||||||
|
entryType == ChangeTime || // Time Change
|
||||||
|
entryType == NewTimeSet || //
|
||||||
|
entryType == ChangeBasalPattern || // Configuration
|
||||||
|
entryType == ClearSettings || //
|
||||||
|
entryType == SaveSettings || //
|
||||||
|
entryType == ChangeMaxBolus || //
|
||||||
|
entryType == ChangeMaxBasal || //
|
||||||
|
entryType == ChangeTempBasalType || //
|
||||||
|
entryType == ChangeBasalProfile_NewProfile || // Basal profile
|
||||||
|
entryType == DailyTotals515 || // Daily Totals
|
||||||
|
entryType == DailyTotals522 || //
|
||||||
|
entryType == DailyTotals523 || //
|
||||||
|
entryType == EndResultTotals
|
||||||
|
}
|
||||||
|
|
||||||
|
val isRelevantEntry: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (type in values()) {
|
||||||
|
opCodeMap[type.code] = type
|
||||||
|
}
|
||||||
|
setSpecialRulesForEntryTypes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val totalLength: Int
|
||||||
|
|
||||||
|
// special rules need to be put in list from highest to lowest (e.g.:
|
||||||
|
// 523andHigher=12, 515andHigher=10 and default (set in cnstr) would be 8)
|
||||||
|
private var specialRulesHead: MutableList<SpecialRule>? = null
|
||||||
|
private var specialRulesBody: MutableList<SpecialRule>? = null
|
||||||
|
private var hasSpecialRules = false
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
fun getTotalLength(medtronicDeviceType: MedtronicDeviceType): Int {
|
||||||
|
return if (hasSpecialRules) {
|
||||||
|
getHeadLength(medtronicDeviceType) + getBodyLength(medtronicDeviceType) + dateLength
|
||||||
|
} else {
|
||||||
|
totalLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSpecialRuleHead(rule: SpecialRule) {
|
||||||
|
if (specialRulesHead.isNullOrEmpty()) {
|
||||||
|
specialRulesHead = ArrayList()
|
||||||
|
}
|
||||||
|
specialRulesHead!!.add(rule)
|
||||||
|
hasSpecialRules = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addSpecialRuleBody(rule: SpecialRule) {
|
||||||
|
if (specialRulesBody.isNullOrEmpty()) {
|
||||||
|
specialRulesBody = ArrayList()
|
||||||
|
}
|
||||||
|
specialRulesBody!!.add(rule)
|
||||||
|
hasSpecialRules = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHeadLength(medtronicDeviceType: MedtronicDeviceType): Int {
|
||||||
|
return if (hasSpecialRules && !specialRulesHead.isNullOrEmpty()) {
|
||||||
|
determineSizeByRule(medtronicDeviceType, headLength, specialRulesHead!!)
|
||||||
|
} else {
|
||||||
|
headLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBodyLength(medtronicDeviceType: MedtronicDeviceType): Int {
|
||||||
|
return if (hasSpecialRules && !specialRulesBody.isNullOrEmpty()) {
|
||||||
|
determineSizeByRule(medtronicDeviceType, bodyLength, specialRulesBody!!)
|
||||||
|
} else {
|
||||||
|
bodyLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// byte[] dh = { 2, 3 };
|
||||||
|
private fun determineSizeByRule(medtronicDeviceType: MedtronicDeviceType, defaultValue: Int, rules: List<SpecialRule>): Int {
|
||||||
|
var size = defaultValue
|
||||||
|
for (rule in rules) {
|
||||||
|
if (MedtronicDeviceType.isSameDevice(medtronicDeviceType, rule.deviceType)) {
|
||||||
|
size = rule.size
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpecialRule internal constructor(var deviceType: MedtronicDeviceType, var size: Int)
|
||||||
|
|
||||||
|
init {
|
||||||
|
totalLength = headLength + dateLength + bodyLength
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,177 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* History page contains data, sorted from newest to oldest (0=newest..n=oldest)
|
|
||||||
* <p>
|
|
||||||
* Created by andy on 9/23/18.
|
|
||||||
*/
|
|
||||||
public class PumpHistoryResult {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
|
|
||||||
private boolean searchFinished = false;
|
|
||||||
private final PumpHistoryEntry searchEntry = null;
|
|
||||||
private Long searchDate = null;
|
|
||||||
private SearchType searchType = SearchType.None;
|
|
||||||
public List<PumpHistoryEntry> unprocessedEntries;
|
|
||||||
public List<PumpHistoryEntry> validEntries;
|
|
||||||
|
|
||||||
|
|
||||||
public PumpHistoryResult(AAPSLogger aapsLogger, PumpHistoryEntry searchEntry, Long targetDate) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
if (searchEntry != null) {
|
|
||||||
/*
|
|
||||||
* this.searchEntry = searchEntry;
|
|
||||||
* this.searchType = SearchType.LastEntry;
|
|
||||||
* aapsLogger.debug(LTag.PUMPCOMM,"PumpHistoryResult. Search parameters: Last Entry: " + searchEntry.atechDateTime + " type="
|
|
||||||
* + searchEntry.getEntryType().name());
|
|
||||||
*/
|
|
||||||
this.searchDate = searchEntry.atechDateTime;
|
|
||||||
this.searchType = SearchType.Date;
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PumpHistoryResult. Search parameters: Date(with searchEntry): " + targetDate);
|
|
||||||
} else if (targetDate != null) {
|
|
||||||
this.searchDate = targetDate;
|
|
||||||
this.searchType = SearchType.Date;
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PumpHistoryResult. Search parameters: Date: " + targetDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this.unprocessedEntries = new ArrayList<>();
|
|
||||||
this.validEntries = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addHistoryEntries(List<PumpHistoryEntry> entries, int page) {
|
|
||||||
this.unprocessedEntries = entries;
|
|
||||||
//aapsLogger.debug(LTag.PUMPCOMM,"PumpHistoryResult. Unprocessed entries: {}", MedtronicUtil.getGsonInstance().toJson(entries));
|
|
||||||
processEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Bug #145 need to check if we had timeChange that went -1, that situation needs to be evaluated separately
|
|
||||||
public void processEntries() {
|
|
||||||
int olderEntries = 0;
|
|
||||||
|
|
||||||
Collections.reverse(this.unprocessedEntries);
|
|
||||||
|
|
||||||
switch (searchType) {
|
|
||||||
case None:
|
|
||||||
//aapsLogger.debug(LTag.PUMPCOMM,"PE. None search");
|
|
||||||
this.validEntries.addAll(this.unprocessedEntries);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LastEntry: {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PE. Last entry search");
|
|
||||||
|
|
||||||
//Collections.sort(this.unprocessedEntries, new PumpHistoryEntry.Comparator());
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PE. PumpHistoryResult. Search entry date: " + searchEntry.atechDateTime);
|
|
||||||
|
|
||||||
Long date = searchEntry.atechDateTime;
|
|
||||||
|
|
||||||
for (PumpHistoryEntry unprocessedEntry : unprocessedEntries) {
|
|
||||||
|
|
||||||
if (unprocessedEntry.equals(searchEntry)) {
|
|
||||||
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Item found {}.", unprocessedEntry);
|
|
||||||
searchFinished = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Entry {} added.", unprocessedEntry);
|
|
||||||
this.validEntries.add(unprocessedEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Date: {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PE. Date search: Search date: " + this.searchDate);
|
|
||||||
|
|
||||||
|
|
||||||
for (PumpHistoryEntry unprocessedEntry : unprocessedEntries) {
|
|
||||||
|
|
||||||
if (unprocessedEntry.atechDateTime == null || unprocessedEntry.atechDateTime == 0) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "PE. PumpHistoryResult. Search entry date: Entry with no date: " + unprocessedEntry);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unprocessedEntry.isAfter(this.searchDate)) {
|
|
||||||
this.validEntries.add(unprocessedEntry);
|
|
||||||
} else {
|
|
||||||
// aapsLogger.debug(LTag.PUMPCOMM,"PE. PumpHistoryResult. Not after.. Unprocessed Entry [year={},entry={}]",
|
|
||||||
// DateTimeUtil.getYear(unprocessedEntry.atechDateTime), unprocessedEntry);
|
|
||||||
if (DateTimeUtil.getYear(unprocessedEntry.atechDateTime) > 2015)
|
|
||||||
olderEntries++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (olderEntries > 0) {
|
|
||||||
//Collections.sort(this.validEntries, new PumpHistoryEntry.Comparator());
|
|
||||||
|
|
||||||
searchFinished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
} // switch
|
|
||||||
|
|
||||||
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Valid Entries: {}", validEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "PumpHistoryResult [unprocessed=" + (unprocessedEntries != null ? "" + unprocessedEntries.size() : "0") + //
|
|
||||||
", valid=" + (validEntries != null ? "" + validEntries.size() : "0") + //
|
|
||||||
", searchEntry=" + searchEntry + //
|
|
||||||
", searchDate=" + searchDate + //
|
|
||||||
", searchType=" + searchType + //
|
|
||||||
", searchFinished=" + searchFinished + //
|
|
||||||
"]";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return latest entry (entry with highest date time)
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public PumpHistoryEntry getLatestEntry() {
|
|
||||||
if (this.validEntries == null || this.validEntries.size() == 0)
|
|
||||||
return null;
|
|
||||||
else {
|
|
||||||
return this.validEntries.get(0);
|
|
||||||
// PumpHistoryEntry pumpHistoryEntry = this.validEntries.get(0);
|
|
||||||
//
|
|
||||||
// if (pumpHistoryEntry.getEntryType() == PumpHistoryEntryType.EndResultTotals)
|
|
||||||
// return pumpHistoryEntry;
|
|
||||||
// else
|
|
||||||
// return this.validEntries.get(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isSearchRequired() {
|
|
||||||
return searchType != SearchType.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isSearchFinished() {
|
|
||||||
return searchFinished;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<PumpHistoryEntry> getValidEntries() {
|
|
||||||
return validEntries;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SearchType {
|
|
||||||
None, //
|
|
||||||
LastEntry, //
|
|
||||||
Date
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* History page contains data, sorted from newest to oldest (0=newest..n=oldest)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Created by andy on 9/23/18.
|
||||||
|
*/
|
||||||
|
class PumpHistoryResult(private val aapsLogger: AAPSLogger, searchEntry: PumpHistoryEntry?, targetDate: Long?) {
|
||||||
|
|
||||||
|
var isSearchFinished = false
|
||||||
|
private set
|
||||||
|
private val searchEntry: PumpHistoryEntry? = null
|
||||||
|
private var searchDate: Long? = null
|
||||||
|
private var searchType = SearchType.None
|
||||||
|
var unprocessedEntries: List<PumpHistoryEntry> = ArrayList()
|
||||||
|
var validEntries: MutableList<PumpHistoryEntry> = ArrayList()
|
||||||
|
|
||||||
|
fun addHistoryEntries(entries: List<PumpHistoryEntry> /*, page: Int*/) {
|
||||||
|
unprocessedEntries = entries
|
||||||
|
//aapsLogger.debug(LTag.PUMPCOMM,"PumpHistoryResult. Unprocessed entries: {}", MedtronicUtil.getGsonInstance().toJson(entries));
|
||||||
|
processEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Bug #145 need to check if we had timeChange that went -1, that situation needs to be evaluated separately
|
||||||
|
fun processEntries() {
|
||||||
|
var olderEntries = 0
|
||||||
|
Collections.reverse(unprocessedEntries)
|
||||||
|
when (searchType) {
|
||||||
|
SearchType.None -> //aapsLogger.debug(LTag.PUMPCOMM,"PE. None search");
|
||||||
|
validEntries.addAll(unprocessedEntries)
|
||||||
|
|
||||||
|
SearchType.LastEntry -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PE. Last entry search")
|
||||||
|
|
||||||
|
//Collections.sort(this.unprocessedEntries, new PumpHistoryEntry.Comparator());
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PE. PumpHistoryResult. Search entry date: " + searchEntry!!.atechDateTime)
|
||||||
|
//val date = searchEntry.atechDateTime
|
||||||
|
for (unprocessedEntry in unprocessedEntries) {
|
||||||
|
if (unprocessedEntry.equals(searchEntry)) {
|
||||||
|
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Item found {}.", unprocessedEntry);
|
||||||
|
isSearchFinished = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Entry {} added.", unprocessedEntry);
|
||||||
|
|
||||||
|
validEntries.add(unprocessedEntry)
|
||||||
|
}
|
||||||
|
// TODO 5minutes back
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchType.Date -> {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PE. Date search: Search date: " + searchDate)
|
||||||
|
for (unprocessedEntry in unprocessedEntries) {
|
||||||
|
if (unprocessedEntry.atechDateTime == 0L) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PE. PumpHistoryResult. Search entry date: Entry with no date: $unprocessedEntry")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (unprocessedEntry.isAfter(searchDate!!)) {
|
||||||
|
validEntries.add(unprocessedEntry)
|
||||||
|
} else {
|
||||||
|
// aapsLogger.debug(LTag.PUMPCOMM,"PE. PumpHistoryResult. Not after.. Unprocessed Entry [year={},entry={}]",
|
||||||
|
// DateTimeUtil.getYear(unprocessedEntry.atechDateTime), unprocessedEntry);
|
||||||
|
if (DateTimeUtil.getYear(unprocessedEntry.atechDateTime) > 2015) olderEntries++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (olderEntries > 0) {
|
||||||
|
//Collections.sort(this.validEntries, new PumpHistoryEntry.Comparator());
|
||||||
|
isSearchFinished = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//aapsLogger.debug(LTag.PUMPCOMM,"PE. Valid Entries: {}", validEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "PumpHistoryResult [unprocessed=" + unprocessedEntries.size + //
|
||||||
|
", valid=" + validEntries.size + //
|
||||||
|
", searchEntry=" + searchEntry + //
|
||||||
|
", searchDate=" + searchDate + //
|
||||||
|
", searchType=" + searchType + //
|
||||||
|
", searchFinished=" + isSearchFinished + //
|
||||||
|
"]"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return latest entry (entry with highest date time)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
val latestEntry: PumpHistoryEntry?
|
||||||
|
get() = if (validEntries.size == 0) null else validEntries[0]
|
||||||
|
|
||||||
|
// val isSearchRequired: Boolean
|
||||||
|
// get() = searchType != SearchType.None
|
||||||
|
|
||||||
|
internal enum class SearchType {
|
||||||
|
None, //
|
||||||
|
LastEntry, //
|
||||||
|
Date
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (searchEntry != null) {
|
||||||
|
/*
|
||||||
|
* this.searchEntry = searchEntry;
|
||||||
|
* this.searchType = SearchType.LastEntry;
|
||||||
|
* aapsLogger.debug(LTag.PUMPCOMM,"PumpHistoryResult. Search parameters: Last Entry: " + searchEntry.atechDateTime + " type="
|
||||||
|
* + searchEntry.getEntryType().name());
|
||||||
|
*/
|
||||||
|
searchDate = searchEntry.atechDateTime
|
||||||
|
searchType = SearchType.Date
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PumpHistoryResult. Search parameters: Date(with searchEntry): $targetDate")
|
||||||
|
} else if (targetDate != null) {
|
||||||
|
searchDate = targetDate
|
||||||
|
searchType = SearchType.Date
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "PumpHistoryResult. Search parameters: Date: $targetDate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.unprocessedEntries = new ArrayList<>();
|
||||||
|
//validEntries = ArrayList()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 6/2/16.
|
|
||||||
*/
|
|
||||||
public class CarelinkLongMessageBody extends MessageBody {
|
|
||||||
|
|
||||||
private static final int LONG_MESSAGE_BODY_LENGTH = 65;
|
|
||||||
protected byte[] data;
|
|
||||||
|
|
||||||
|
|
||||||
CarelinkLongMessageBody() {
|
|
||||||
init(new byte[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public CarelinkLongMessageBody(byte[] payload) {
|
|
||||||
init(payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
|
|
||||||
if (rxData != null && rxData.length == LONG_MESSAGE_BODY_LENGTH) {
|
|
||||||
data = rxData;
|
|
||||||
} else {
|
|
||||||
data = new byte[LONG_MESSAGE_BODY_LENGTH];
|
|
||||||
if (rxData != null) {
|
|
||||||
int size = rxData.length < LONG_MESSAGE_BODY_LENGTH ? rxData.length : LONG_MESSAGE_BODY_LENGTH;
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
data[i] = rxData[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLength() {
|
|
||||||
return LONG_MESSAGE_BODY_LENGTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getTxData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/2/16.
|
||||||
|
*/
|
||||||
|
open class CarelinkLongMessageBody : MessageBody {
|
||||||
|
|
||||||
|
//protected var data: ByteArray? = null
|
||||||
|
|
||||||
|
internal constructor() {
|
||||||
|
init(ByteArray(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(payload: ByteArray) {
|
||||||
|
init(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun init(rxData: ByteArray?) {
|
||||||
|
if (rxData != null && rxData.size == LONG_MESSAGE_BODY_LENGTH) {
|
||||||
|
data = rxData
|
||||||
|
} else {
|
||||||
|
data = ByteArray(LONG_MESSAGE_BODY_LENGTH)
|
||||||
|
if (rxData != null) {
|
||||||
|
val size = if (rxData.size < LONG_MESSAGE_BODY_LENGTH) rxData.size else LONG_MESSAGE_BODY_LENGTH
|
||||||
|
for (i in 0 until size) {
|
||||||
|
data!![i] = rxData[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = LONG_MESSAGE_BODY_LENGTH
|
||||||
|
|
||||||
|
// {
|
||||||
|
// return LONG_MESSAGE_BODY_LENGTH
|
||||||
|
// }
|
||||||
|
|
||||||
|
// override fun getTxData(): ByteArray {
|
||||||
|
// return data
|
||||||
|
// }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LONG_MESSAGE_BODY_LENGTH = 65
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,53 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
*/
|
|
||||||
// Andy: See comments in message body
|
|
||||||
public class CarelinkShortMessageBody extends MessageBody {
|
|
||||||
|
|
||||||
byte[] body;
|
|
||||||
|
|
||||||
|
|
||||||
public CarelinkShortMessageBody() {
|
|
||||||
init(new byte[] { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public CarelinkShortMessageBody(byte[] data) {
|
|
||||||
init(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLength() {
|
|
||||||
return body.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
body = rxData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getRxData() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRxData(byte[] rxData) {
|
|
||||||
init(rxData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getTxData() {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setTxData(byte[] txData) {
|
|
||||||
init(txData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
*/
|
||||||
|
// Andy: See comments in message body
|
||||||
|
open class CarelinkShortMessageBody : MessageBody {
|
||||||
|
|
||||||
|
//var body: ByteArray?
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
init(byteArrayOf(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(data: ByteArray?) {
|
||||||
|
init(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = data!!.size
|
||||||
|
|
||||||
|
override fun init(rxData: ByteArray?) {
|
||||||
|
data = rxData
|
||||||
|
}
|
||||||
|
|
||||||
|
var rxData: ByteArray?
|
||||||
|
get() = data
|
||||||
|
set(rxData) {
|
||||||
|
init(rxData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// override var txData: ByteArray?
|
||||||
|
// get() = body
|
||||||
|
// set(txData) {
|
||||||
|
// init(txData)
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 6/2/16.
|
|
||||||
*/
|
|
||||||
public class GetHistoryPageCarelinkMessageBody extends CarelinkLongMessageBody {
|
|
||||||
|
|
||||||
// public boolean wasLastFrame = false;
|
|
||||||
// public int frameNumber = 0;
|
|
||||||
// public byte[] frame = new byte[] {};
|
|
||||||
|
|
||||||
public GetHistoryPageCarelinkMessageBody(byte[] frameData) {
|
|
||||||
init(frameData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public GetHistoryPageCarelinkMessageBody(int pageNum) {
|
|
||||||
init(pageNum);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLength() {
|
|
||||||
return data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
super.init(rxData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void init(int pageNum) {
|
|
||||||
byte numArgs = 1;
|
|
||||||
super.init(new byte[] { numArgs, (byte)pageNum });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getFrameNumber() {
|
|
||||||
if (data.length > 0) {
|
|
||||||
return data[0] & 0x7f;
|
|
||||||
}
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean wasLastFrame() {
|
|
||||||
return (data[0] & 0x80) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getFrameData() {
|
|
||||||
return ByteUtil.substring(data, 1, data.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import kotlin.experimental.and
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/2/16.
|
||||||
|
*/
|
||||||
|
class GetHistoryPageCarelinkMessageBody : CarelinkLongMessageBody {
|
||||||
|
|
||||||
|
// public boolean wasLastFrame = false;
|
||||||
|
// public int frameNumber = 0;
|
||||||
|
// public byte[] frame = new byte[] {};
|
||||||
|
constructor(frameData: ByteArray?) {
|
||||||
|
init(frameData)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(pageNum: Int) {
|
||||||
|
init(pageNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = data!!.size
|
||||||
|
|
||||||
|
override fun init(rxData: ByteArray?) {
|
||||||
|
super.init(rxData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(pageNum: Int) {
|
||||||
|
val numArgs: Byte = 1
|
||||||
|
super.init(byteArrayOf(numArgs, pageNum.toByte()))
|
||||||
|
}
|
||||||
|
|
||||||
|
val frameNumber: Int
|
||||||
|
get() = if (data!!.size > 0) {
|
||||||
|
(data!![0] and 0x7f.toByte()).toInt()
|
||||||
|
} else 255
|
||||||
|
|
||||||
|
fun wasLastFrame(): Boolean {
|
||||||
|
return (data!![0] and 0x80.toByte()).toInt() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val frameData: ByteArray
|
||||||
|
get() {
|
||||||
|
return if (data == null) {
|
||||||
|
byteArrayOf()
|
||||||
|
} else {
|
||||||
|
ByteUtil.substring(data!!, 1, data!!.size - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
*/
|
|
||||||
public class MessageBody {
|
|
||||||
|
|
||||||
public int getLength() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getTxData() {
|
|
||||||
return new byte[]{};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder(getClass().getSimpleName());
|
|
||||||
|
|
||||||
sb.append(" [txData=");
|
|
||||||
sb.append(ByteUtil.shortHexString(getTxData()));
|
|
||||||
sb.append("]");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
*/
|
||||||
|
open class MessageBody {
|
||||||
|
|
||||||
|
protected var data: ByteArray? = null
|
||||||
|
|
||||||
|
open val length: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
open fun init(rxData: ByteArray?) {}
|
||||||
|
|
||||||
|
open val txData: ByteArray?
|
||||||
|
get() = if (data == null) byteArrayOf() else data
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder(javaClass.simpleName)
|
||||||
|
sb.append(" [txData=")
|
||||||
|
sb.append(ByteUtil.shortHexString(txData))
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
* refactored into enum
|
|
||||||
*/
|
|
||||||
public enum PacketType {
|
|
||||||
Invalid(0x00), //
|
|
||||||
MySentry(0xa2), //
|
|
||||||
Meter(0xa5), //
|
|
||||||
Carelink(0xa7), //
|
|
||||||
Sensor(0xa8) //
|
|
||||||
;
|
|
||||||
|
|
||||||
public static Map<Byte, PacketType> mapByValue;
|
|
||||||
|
|
||||||
static {
|
|
||||||
mapByValue = new HashMap<>();
|
|
||||||
|
|
||||||
for (PacketType packetType : values()) {
|
|
||||||
mapByValue.put(packetType.value, packetType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final byte value;
|
|
||||||
|
|
||||||
|
|
||||||
PacketType(int value) {
|
|
||||||
this.value = (byte)value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static PacketType getByValue(short value) {
|
|
||||||
if (mapByValue.containsKey(value))
|
|
||||||
return mapByValue.get(value);
|
|
||||||
else
|
|
||||||
return PacketType.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
* refactored into enum
|
||||||
|
*/
|
||||||
|
enum class PacketType(value: Int) {
|
||||||
|
|
||||||
|
Invalid(0x00), //
|
||||||
|
MySentry(0xa2), //
|
||||||
|
Meter(0xa5), //
|
||||||
|
Carelink(0xa7), //
|
||||||
|
Sensor(0xa8 //
|
||||||
|
);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var mapByValue: MutableMap<Byte, PacketType> = HashMap()
|
||||||
|
|
||||||
|
fun getByValue(value: Short): PacketType? {
|
||||||
|
return if (mapByValue.containsKey(value.toByte())) mapByValue.get(value.toByte()) else Invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
for (packetType in values()) {
|
||||||
|
mapByValue[packetType.value] = packetType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val value: Byte
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.value = value.toByte()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
*/
|
|
||||||
public class PumpAckMessageBody extends CarelinkShortMessageBody {
|
|
||||||
|
|
||||||
public PumpAckMessageBody() {
|
|
||||||
init(new byte[] { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpAckMessageBody(byte[] bodyData) {
|
|
||||||
init(bodyData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
*/
|
||||||
|
class PumpAckMessageBody : CarelinkShortMessageBody {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
init(byteArrayOf(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(bodyData: ByteArray?) {
|
||||||
|
init(bodyData)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,214 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
*/
|
|
||||||
public class PumpMessage implements RLMessage {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
|
|
||||||
private PacketType packetType = PacketType.Carelink;
|
|
||||||
public byte[] address = new byte[]{0, 0, 0};
|
|
||||||
public MedtronicCommandType commandType;
|
|
||||||
public Byte invalidCommandType;
|
|
||||||
public MessageBody messageBody = new MessageBody();
|
|
||||||
public String error = null;
|
|
||||||
|
|
||||||
public static final int FRAME_DATA_LENGTH = 64;
|
|
||||||
|
|
||||||
|
|
||||||
public PumpMessage(AAPSLogger aapsLogger, String error) {
|
|
||||||
this.error = error;
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpMessage(AAPSLogger aapsLogger, byte[] rxData) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
init(rxData);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpMessage(AAPSLogger aapsLogger) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isErrorResponse() {
|
|
||||||
return (this.error != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void init(PacketType packetType, byte[] address, MedtronicCommandType commandType, MessageBody messageBody) {
|
|
||||||
this.packetType = packetType;
|
|
||||||
this.address = address;
|
|
||||||
this.commandType = commandType;
|
|
||||||
this.messageBody = messageBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
if (rxData == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (rxData.length > 0) {
|
|
||||||
this.packetType = PacketType.getByValue(rxData[0]);
|
|
||||||
}
|
|
||||||
if (rxData.length > 3) {
|
|
||||||
this.address = ByteUtil.substring(rxData, 1, 3);
|
|
||||||
}
|
|
||||||
if (rxData.length > 4) {
|
|
||||||
this.commandType = MedtronicCommandType.getByCode(rxData[4]);
|
|
||||||
if (this.commandType == MedtronicCommandType.InvalidCommand) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "PumpMessage - Unknown commandType " + rxData[4]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rxData.length > 5) {
|
|
||||||
this.messageBody = MedtronicCommandType.constructMessageBody(commandType,
|
|
||||||
ByteUtil.substring(rxData, 5, rxData.length - 5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getTxData() {
|
|
||||||
byte[] rval = ByteUtil.concat(new byte[]{packetType.getValue()}, address);
|
|
||||||
rval = ByteUtil.concat(rval, commandType.getCommandCode());
|
|
||||||
rval = ByteUtil.concat(rval, messageBody.getTxData());
|
|
||||||
return rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getContents() {
|
|
||||||
return ByteUtil.concat(new byte[]{commandType.getCommandCode()}, messageBody.getTxData());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// rawContent = just response without code (contents-2, messageBody.txData-1);
|
|
||||||
public byte[] getRawContent() {
|
|
||||||
|
|
||||||
if ((messageBody == null) || (messageBody.getTxData() == null) || (messageBody.getTxData().length == 0))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
byte[] data = messageBody.getTxData();
|
|
||||||
|
|
||||||
int length = ByteUtil.asUINT8(data[0]); // length is not always correct so, we check whole array if we have
|
|
||||||
// data, after length
|
|
||||||
int originalLength = length;
|
|
||||||
|
|
||||||
// check if displayed length is invalid
|
|
||||||
if (length > data.length - 1) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check Old Way
|
|
||||||
boolean oldWay = false;
|
|
||||||
for (int i = (length + 1); i < data.length; i++) {
|
|
||||||
if (data[i] != 0x00) {
|
|
||||||
oldWay = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldWay) {
|
|
||||||
length = data.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] arrayOut = new byte[length];
|
|
||||||
|
|
||||||
System.arraycopy(messageBody.getTxData(), 1, arrayOut, 0, length);
|
|
||||||
|
|
||||||
// if (isLogEnabled())
|
|
||||||
// LOG.debug("PumpMessage - Length: " + length + ", Original Length: " + originalLength + ", CommandType: "
|
|
||||||
// + commandType);
|
|
||||||
|
|
||||||
return arrayOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getRawContentOfFrame() {
|
|
||||||
byte[] raw = messageBody.getTxData();
|
|
||||||
if (raw==null || raw.length==0) {
|
|
||||||
return new byte[0];
|
|
||||||
} else {
|
|
||||||
return ByteUtil.substring(raw, 1, Math.min(FRAME_DATA_LENGTH, raw.length - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
if (packetType == null)
|
|
||||||
return false;
|
|
||||||
if (address == null)
|
|
||||||
return false;
|
|
||||||
if (commandType == null)
|
|
||||||
return false;
|
|
||||||
return messageBody != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MessageBody getMessageBody() {
|
|
||||||
return messageBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getResponseContent() {
|
|
||||||
StringBuilder sb = new StringBuilder("PumpMessage [response=");
|
|
||||||
boolean showData = true;
|
|
||||||
|
|
||||||
if (commandType != null) {
|
|
||||||
if (commandType == MedtronicCommandType.CommandACK) {
|
|
||||||
sb.append("Acknowledged");
|
|
||||||
showData = false;
|
|
||||||
} else if (commandType == MedtronicCommandType.CommandNAK) {
|
|
||||||
sb.append("NOT Acknowledged");
|
|
||||||
showData = false;
|
|
||||||
} else {
|
|
||||||
sb.append(commandType.name());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sb.append("Unknown_Type");
|
|
||||||
sb.append(" (" + invalidCommandType + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showData) {
|
|
||||||
sb.append(", rawResponse=");
|
|
||||||
sb.append(ByteUtil.shortHexString(getRawContent()));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append("]");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder("PumpMessage [");
|
|
||||||
|
|
||||||
sb.append("packetType=");
|
|
||||||
sb.append(packetType == null ? "null" : packetType.name());
|
|
||||||
|
|
||||||
sb.append(", address=(");
|
|
||||||
sb.append(ByteUtil.shortHexString(this.address));
|
|
||||||
|
|
||||||
sb.append("), commandType=");
|
|
||||||
sb.append(commandType == null ? "null" : commandType.name());
|
|
||||||
|
|
||||||
if (invalidCommandType != null) {
|
|
||||||
sb.append(", invalidCommandType=");
|
|
||||||
sb.append(invalidCommandType);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(", messageBody=(");
|
|
||||||
sb.append(this.messageBody == null ? "null" : this.messageBody);
|
|
||||||
|
|
||||||
sb.append(")]");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
*/
|
||||||
|
class PumpMessage : RLMessage {
|
||||||
|
|
||||||
|
private val aapsLogger: AAPSLogger
|
||||||
|
private var packetType: PacketType? = PacketType.Carelink
|
||||||
|
var address: ByteArray? = byteArrayOf(0, 0, 0)
|
||||||
|
var commandType: MedtronicCommandType? = null
|
||||||
|
var invalidCommandType: Byte? = null
|
||||||
|
var messageBody: MessageBody? = MessageBody()
|
||||||
|
var error: String? = null
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger, error: String?) {
|
||||||
|
this.error = error
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger, rxData: ByteArray?) {
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
init(rxData)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger) {
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
val isErrorResponse: Boolean
|
||||||
|
get() = error != null
|
||||||
|
|
||||||
|
fun init(packetType: PacketType?, address: ByteArray?, commandType: MedtronicCommandType?, messageBody: MessageBody?) {
|
||||||
|
this.packetType = packetType
|
||||||
|
this.address = address
|
||||||
|
this.commandType = commandType
|
||||||
|
this.messageBody = messageBody
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(rxData: ByteArray?) {
|
||||||
|
if (rxData == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (rxData.size > 0) {
|
||||||
|
packetType = PacketType.getByValue(rxData[0].toShort())
|
||||||
|
}
|
||||||
|
if (rxData.size > 3) {
|
||||||
|
address = ByteUtil.substring(rxData, 1, 3)
|
||||||
|
}
|
||||||
|
if (rxData.size > 4) {
|
||||||
|
commandType = MedtronicCommandType.getByCode(rxData[4])
|
||||||
|
if (commandType == MedtronicCommandType.InvalidCommand) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "PumpMessage - Unknown commandType " + rxData[4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rxData.size > 5) {
|
||||||
|
messageBody = MedtronicCommandType.constructMessageBody(commandType,
|
||||||
|
ByteUtil.substring(rxData, 5, rxData.size - 5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTxData(): ByteArray {
|
||||||
|
var rval = ByteUtil.concat(byteArrayOf(packetType!!.value), address)
|
||||||
|
rval = ByteUtil.concat(rval, commandType!!.commandCode)
|
||||||
|
rval = ByteUtil.concat(rval, messageBody!!.txData)
|
||||||
|
return rval
|
||||||
|
}
|
||||||
|
|
||||||
|
val contents: ByteArray
|
||||||
|
get() = ByteUtil.concat(byteArrayOf(commandType!!.commandCode), messageBody!!.txData)// length is not always correct so, we check whole array if we have
|
||||||
|
// data, after length
|
||||||
|
|
||||||
|
// check if displayed length is invalid
|
||||||
|
|
||||||
|
// check Old Way
|
||||||
|
|
||||||
|
// if (isLogEnabled())
|
||||||
|
// LOG.debug("PumpMessage - Length: " + length + ", Original Length: " + originalLength + ", CommandType: "
|
||||||
|
// + commandType);
|
||||||
|
|
||||||
|
// rawContent = just response without code (contents-2, messageBody.txData-1);
|
||||||
|
val rawContent: ByteArray
|
||||||
|
get() {
|
||||||
|
if (messageBody == null || messageBody!!.txData == null || messageBody!!.txData!!.size == 0) return byteArrayOf()
|
||||||
|
val data = messageBody!!.txData
|
||||||
|
var length = ByteUtil.asUINT8(data!![0]) // length is not always correct so, we check whole array if we have
|
||||||
|
// data, after length
|
||||||
|
//val originalLength = length
|
||||||
|
|
||||||
|
// check if displayed length is invalid
|
||||||
|
if (length > data.size - 1) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// check Old Way
|
||||||
|
var oldWay = false
|
||||||
|
for (i in length + 1 until data.size) {
|
||||||
|
if (data[i] != 0x00.toByte()) {
|
||||||
|
oldWay = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldWay) {
|
||||||
|
length = data.size - 1
|
||||||
|
}
|
||||||
|
val arrayOut = ByteArray(length)
|
||||||
|
System.arraycopy(messageBody!!.txData, 1, arrayOut, 0, length)
|
||||||
|
|
||||||
|
// if (isLogEnabled())
|
||||||
|
// LOG.debug("PumpMessage - Length: " + length + ", Original Length: " + originalLength + ", CommandType: "
|
||||||
|
// + commandType);
|
||||||
|
return arrayOut
|
||||||
|
}
|
||||||
|
|
||||||
|
val rawContentOfFrame: ByteArray
|
||||||
|
get() {
|
||||||
|
val raw = messageBody!!.txData
|
||||||
|
return if (raw == null || raw.size == 0) {
|
||||||
|
byteArrayOf()
|
||||||
|
} else {
|
||||||
|
ByteUtil.substring(raw, 1, Math.min(FRAME_DATA_LENGTH, raw.size - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValid(): Boolean {
|
||||||
|
if (packetType == null) return false
|
||||||
|
if (address == null) return false
|
||||||
|
return if (commandType == null) false else messageBody != null
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseContent: String
|
||||||
|
get() {
|
||||||
|
val sb = StringBuilder("PumpMessage [response=")
|
||||||
|
var showData = true
|
||||||
|
if (commandType != null) {
|
||||||
|
if (commandType == MedtronicCommandType.CommandACK) {
|
||||||
|
sb.append("Acknowledged")
|
||||||
|
showData = false
|
||||||
|
} else if (commandType == MedtronicCommandType.CommandNAK) {
|
||||||
|
sb.append("NOT Acknowledged")
|
||||||
|
showData = false
|
||||||
|
} else {
|
||||||
|
sb.append(commandType!!.name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append("Unknown_Type")
|
||||||
|
sb.append(" ($invalidCommandType)")
|
||||||
|
}
|
||||||
|
if (showData) {
|
||||||
|
sb.append(", rawResponse=")
|
||||||
|
sb.append(ByteUtil.shortHexString(rawContent))
|
||||||
|
}
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder("PumpMessage [")
|
||||||
|
sb.append("packetType=")
|
||||||
|
sb.append(if (packetType == null) "null" else packetType!!.name)
|
||||||
|
sb.append(", address=(")
|
||||||
|
sb.append(ByteUtil.shortHexString(address))
|
||||||
|
sb.append("), commandType=")
|
||||||
|
sb.append(if (commandType == null) "null" else commandType!!.name)
|
||||||
|
if (invalidCommandType != null) {
|
||||||
|
sb.append(", invalidCommandType=")
|
||||||
|
sb.append(invalidCommandType)
|
||||||
|
}
|
||||||
|
sb.append(", messageBody=(")
|
||||||
|
sb.append(if (messageBody == null) "null" else messageBody)
|
||||||
|
sb.append(")]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val FRAME_DATA_LENGTH = 64
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/16.
|
|
||||||
*/
|
|
||||||
public class UnknownMessageBody extends MessageBody {
|
|
||||||
|
|
||||||
public byte[] rxData;
|
|
||||||
|
|
||||||
|
|
||||||
public UnknownMessageBody(byte[] data) {
|
|
||||||
this.rxData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLength() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(byte[] rxData) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getRxData() {
|
|
||||||
return rxData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRxData(byte[] rxData) {
|
|
||||||
this.rxData = rxData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getTxData() {
|
|
||||||
return rxData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setTxData(byte[] txData) {
|
|
||||||
this.rxData = txData;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.message
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/16.
|
||||||
|
*/
|
||||||
|
class UnknownMessageBody(override var txData: ByteArray) : MessageBody() {
|
||||||
|
|
||||||
|
override val length: Int
|
||||||
|
get() = 0
|
||||||
|
|
||||||
|
override fun init(rxData: ByteArray?) {
|
||||||
|
data = rxData
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,58 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui;
|
|
||||||
|
|
||||||
import dagger.android.HasAndroidInjector;
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/14/18.
|
|
||||||
*/
|
|
||||||
public class MedtronicUIComm {
|
|
||||||
|
|
||||||
private final HasAndroidInjector injector;
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
private final MedtronicUtil medtronicUtil;
|
|
||||||
private final MedtronicCommunicationManager medtronicCommunicationManager;
|
|
||||||
private final MedtronicUIPostprocessor medtronicUIPostprocessor;
|
|
||||||
|
|
||||||
public MedtronicUIComm(
|
|
||||||
HasAndroidInjector injector,
|
|
||||||
AAPSLogger aapsLogger,
|
|
||||||
MedtronicUtil medtronicUtil,
|
|
||||||
MedtronicUIPostprocessor medtronicUIPostprocessor,
|
|
||||||
MedtronicCommunicationManager medtronicCommunicationManager
|
|
||||||
) {
|
|
||||||
this.injector = injector;
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.medtronicUtil = medtronicUtil;
|
|
||||||
this.medtronicUIPostprocessor = medtronicUIPostprocessor;
|
|
||||||
this.medtronicCommunicationManager = medtronicCommunicationManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized MedtronicUITask executeCommand(MedtronicCommandType commandType, Object... parameters) {
|
|
||||||
|
|
||||||
aapsLogger.info(LTag.PUMP, "Execute Command: " + commandType.name());
|
|
||||||
|
|
||||||
MedtronicUITask task = new MedtronicUITask(injector, commandType, parameters);
|
|
||||||
|
|
||||||
medtronicUtil.setCurrentCommand(commandType);
|
|
||||||
|
|
||||||
task.execute(medtronicCommunicationManager);
|
|
||||||
|
|
||||||
if (!task.isReceived()) {
|
|
||||||
aapsLogger.warn(LTag.PUMP, "Reply not received for " + commandType);
|
|
||||||
}
|
|
||||||
|
|
||||||
task.postProcess(medtronicUIPostprocessor);
|
|
||||||
|
|
||||||
return task;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInvalidResponsesCount() {
|
|
||||||
return medtronicCommunicationManager.getNotConnectedCount();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/14/18.
|
||||||
|
*/
|
||||||
|
class MedtronicUIComm @Inject constructor(
|
||||||
|
private val injector: HasAndroidInjector,
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val medtronicUtil: MedtronicUtil,
|
||||||
|
private val medtronicUIPostprocessor: MedtronicUIPostprocessor,
|
||||||
|
private val medtronicCommunicationManager: MedtronicCommunicationManager
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun executeCommand(commandType: MedtronicCommandType): MedtronicUITask {
|
||||||
|
return executeCommand(commandType, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun executeCommand(commandType: MedtronicCommandType, parameters: ArrayList<Any>?): MedtronicUITask {
|
||||||
|
|
||||||
|
aapsLogger.info(LTag.PUMP, "Execute Command: " + commandType.name)
|
||||||
|
val task = MedtronicUITask(injector, commandType, parameters)
|
||||||
|
medtronicUtil.setCurrentCommand(commandType)
|
||||||
|
task.execute(medtronicCommunicationManager)
|
||||||
|
|
||||||
|
if (!task.isReceived) {
|
||||||
|
aapsLogger.warn(LTag.PUMP, "Reply not received for $commandType")
|
||||||
|
}
|
||||||
|
|
||||||
|
task.postProcess(medtronicUIPostprocessor)
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
|
val invalidResponsesCount: Int
|
||||||
|
get() = medtronicCommunicationManager.notConnectedCount
|
||||||
|
|
||||||
|
}
|
|
@ -1,252 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui;
|
|
||||||
|
|
||||||
import org.joda.time.DateTimeZone;
|
|
||||||
import org.joda.time.Duration;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.BasalProfileStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/15/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class MedtronicUIPostprocessor {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
private final RxBusWrapper rxBus;
|
|
||||||
private final ResourceHelper resourceHelper;
|
|
||||||
private final MedtronicUtil medtronicUtil;
|
|
||||||
private final MedtronicPumpStatus medtronicPumpStatus;
|
|
||||||
private final MedtronicPumpPlugin medtronicPumpPlugin;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MedtronicUIPostprocessor(
|
|
||||||
AAPSLogger aapsLogger,
|
|
||||||
RxBusWrapper rxBus,
|
|
||||||
ResourceHelper resourceHelper,
|
|
||||||
MedtronicUtil medtronicUtil,
|
|
||||||
MedtronicPumpStatus medtronicPumpStatus,
|
|
||||||
MedtronicPumpPlugin medtronicPumpPlugin) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.rxBus = rxBus;
|
|
||||||
this.resourceHelper = resourceHelper;
|
|
||||||
this.medtronicUtil = medtronicUtil;
|
|
||||||
this.medtronicPumpStatus = medtronicPumpStatus;
|
|
||||||
this.medtronicPumpPlugin = medtronicPumpPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this is mostly intended for command that return certain statuses (Remaining Insulin, ...), and
|
|
||||||
// where responses won't be directly used
|
|
||||||
void postProcessData(MedtronicUITask uiTask) {
|
|
||||||
|
|
||||||
switch (uiTask.commandType) {
|
|
||||||
|
|
||||||
case SetBasalProfileSTD: {
|
|
||||||
Boolean response = (Boolean) uiTask.returnData;
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
BasalProfile basalProfile = (BasalProfile) uiTask.getParameter(0);
|
|
||||||
|
|
||||||
medtronicPumpStatus.basalsByHour = basalProfile.getProfilesByHour(medtronicPumpPlugin.getPumpDescription().getPumpType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetBasalProfileSTD: {
|
|
||||||
BasalProfile basalProfile = (BasalProfile) uiTask.returnData;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Double[] profilesByHour = basalProfile.getProfilesByHour(medtronicPumpPlugin.getPumpDescription().getPumpType());
|
|
||||||
|
|
||||||
if (profilesByHour != null) {
|
|
||||||
medtronicPumpStatus.basalsByHour = profilesByHour;
|
|
||||||
medtronicPumpStatus.basalProfileStatus = BasalProfileStatus.ProfileOK;
|
|
||||||
} else {
|
|
||||||
uiTask.responseType = MedtronicUIResponseType.Error;
|
|
||||||
uiTask.errorDescription = "No profile found.";
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Basal Profile was NOT valid. [%s]", basalProfile.basalProfileToStringError()));
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Basal Profile was returned, but was invalid. [%s]", basalProfile.basalProfileToStringError()));
|
|
||||||
uiTask.responseType = MedtronicUIResponseType.Error;
|
|
||||||
uiTask.errorDescription = "No profile found.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetBolus: {
|
|
||||||
medtronicPumpStatus.lastBolusAmount = uiTask.getDoubleFromParameters(0);
|
|
||||||
medtronicPumpStatus.lastBolusTime = new Date();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetRemainingInsulin: {
|
|
||||||
medtronicPumpStatus.reservoirRemainingUnits = (Float) uiTask.returnData;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CancelTBR: {
|
|
||||||
medtronicPumpStatus.tempBasalStart = null;
|
|
||||||
medtronicPumpStatus.tempBasalAmount = null;
|
|
||||||
medtronicPumpStatus.tempBasalLength = null;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetRealTimeClock: {
|
|
||||||
processTime(uiTask);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetRealTimeClock: {
|
|
||||||
boolean response = (Boolean) uiTask.returnData;
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "New time was %s set.", response ? "" : "NOT"));
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
medtronicUtil.getPumpTime().timeDifference = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
case GetBatteryStatus: {
|
|
||||||
BatteryStatusDTO batteryStatusDTO = (BatteryStatusDTO) uiTask.returnData;
|
|
||||||
|
|
||||||
medtronicPumpStatus.batteryRemaining = batteryStatusDTO.getCalculatedPercent(medtronicPumpStatus.batteryType);
|
|
||||||
|
|
||||||
if (batteryStatusDTO.voltage != null) {
|
|
||||||
medtronicPumpStatus.batteryVoltage = batteryStatusDTO.voltage;
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "BatteryStatus: %s", batteryStatusDTO.toString()));
|
|
||||||
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PumpModel: {
|
|
||||||
if (medtronicPumpStatus.medtronicDeviceType != medtronicUtil.getMedtronicPumpModel()) {
|
|
||||||
aapsLogger.warn(LTag.PUMP, "Configured pump is different then pump detected !");
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpTypeNotSame, resourceHelper, rxBus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Settings_512:
|
|
||||||
case Settings: {
|
|
||||||
postProcessSettings(uiTask);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// no postprocessing
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
//aapsLogger.error(LTag.PUMP, "Post-processing not implemented for {}.", uiTask.commandType.name());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void processTime(MedtronicUITask uiTask) {
|
|
||||||
|
|
||||||
ClockDTO clockDTO = (ClockDTO) uiTask.returnData;
|
|
||||||
|
|
||||||
Duration dur = new Duration(clockDTO.pumpTime.toDateTime(DateTimeZone.UTC),
|
|
||||||
clockDTO.localDeviceTime.toDateTime(DateTimeZone.UTC));
|
|
||||||
|
|
||||||
clockDTO.timeDifference = (int) dur.getStandardSeconds();
|
|
||||||
|
|
||||||
medtronicUtil.setPumpTime(clockDTO);
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "Pump Time: " + clockDTO.localDeviceTime + ", DeviceTime=" + clockDTO.pumpTime + //
|
|
||||||
", diff: " + dur.getStandardSeconds() + " s");
|
|
||||||
|
|
||||||
// if (dur.getStandardMinutes() >= 10) {
|
|
||||||
// if (isLogEnabled())
|
|
||||||
// LOG.warn("Pump clock needs update, pump time: " + clockDTO.pumpTime.toString("HH:mm:ss") + " (difference: "
|
|
||||||
// + dur.getStandardSeconds() + " s)");
|
|
||||||
// sendNotification(MedtronicNotificationType.PumpWrongTimeUrgent);
|
|
||||||
// } else if (dur.getStandardMinutes() >= 4) {
|
|
||||||
// if (isLogEnabled())
|
|
||||||
// LOG.warn("Pump clock needs update, pump time: " + clockDTO.pumpTime.toString("HH:mm:ss") + " (difference: "
|
|
||||||
// + dur.getStandardSeconds() + " s)");
|
|
||||||
// sendNotification(MedtronicNotificationType.PumpWrongTimeNormal);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void postProcessSettings(MedtronicUITask uiTask) {
|
|
||||||
|
|
||||||
Map<String, PumpSettingDTO> settings = (Map<String, PumpSettingDTO>) uiTask.returnData;
|
|
||||||
|
|
||||||
medtronicUtil.setSettings(settings);
|
|
||||||
|
|
||||||
PumpSettingDTO checkValue;
|
|
||||||
|
|
||||||
medtronicPumpPlugin.getRileyLinkService().verifyConfiguration();
|
|
||||||
|
|
||||||
// check profile
|
|
||||||
if (!"Yes".equals(settings.get("PCFG_BASAL_PROFILES_ENABLED").value)) {
|
|
||||||
aapsLogger.error(LTag.PUMP, "Basal profiles are not enabled on pump.");
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpBasalProfilesNotEnabled, resourceHelper, rxBus);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
checkValue = settings.get("PCFG_ACTIVE_BASAL_PROFILE");
|
|
||||||
|
|
||||||
if (!"STD".equals(checkValue.value)) {
|
|
||||||
aapsLogger.error("Basal profile set on pump is incorrect (must be STD).");
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpIncorrectBasalProfileSelected, resourceHelper, rxBus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TBR
|
|
||||||
|
|
||||||
checkValue = settings.get("PCFG_TEMP_BASAL_TYPE");
|
|
||||||
|
|
||||||
if (!"Units".equals(checkValue.value)) {
|
|
||||||
aapsLogger.error("Wrong TBR type set on pump (must be Absolute).");
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongTBRTypeSet, resourceHelper, rxBus);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAXes
|
|
||||||
|
|
||||||
checkValue = settings.get("PCFG_MAX_BOLUS");
|
|
||||||
|
|
||||||
if (!MedtronicUtil.isSame(Double.parseDouble(checkValue.value), medtronicPumpStatus.maxBolus)) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Wrong Max Bolus set on Pump (current=%s, required=%.2f).", checkValue.value, medtronicPumpStatus.maxBolus));
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongMaxBolusSet, resourceHelper, rxBus, medtronicPumpStatus.maxBolus);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkValue = settings.get("PCFG_MAX_BASAL");
|
|
||||||
|
|
||||||
if (!MedtronicUtil.isSame(Double.parseDouble(checkValue.value), medtronicPumpStatus.maxBasal)) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Wrong Max Basal set on Pump (current=%s, required=%.2f).", checkValue.value, medtronicPumpStatus.maxBasal));
|
|
||||||
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongMaxBasalSet, resourceHelper, rxBus, medtronicPumpStatus.maxBasal);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.BasalProfileStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
|
import org.joda.time.DateTimeZone
|
||||||
|
import org.joda.time.Duration
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/15/18.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class MedtronicUIPostprocessor @Inject constructor(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val rxBus: RxBusWrapper,
|
||||||
|
private val resourceHelper: ResourceHelper,
|
||||||
|
private val medtronicUtil: MedtronicUtil,
|
||||||
|
private val medtronicPumpStatus: MedtronicPumpStatus,
|
||||||
|
private val medtronicPumpPlugin: MedtronicPumpPlugin) {
|
||||||
|
|
||||||
|
// this is mostly intended for command that return certain statuses (Remaining Insulin, ...), and
|
||||||
|
// where responses won't be directly used
|
||||||
|
fun postProcessData(uiTask: MedtronicUITask) {
|
||||||
|
when (uiTask.commandType) {
|
||||||
|
MedtronicCommandType.SetBasalProfileSTD -> {
|
||||||
|
val response = uiTask.result as Boolean?
|
||||||
|
if (response != null && response) {
|
||||||
|
val basalProfile = uiTask.getParameter(0) as BasalProfile
|
||||||
|
aapsLogger.debug("D: basal profile returned after set: $basalProfile")
|
||||||
|
|
||||||
|
medtronicPumpStatus.basalsByHour = basalProfile.getProfilesByHour(medtronicPumpPlugin.pumpDescription.pumpType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetBasalProfileSTD -> {
|
||||||
|
val basalProfile = uiTask.result as BasalProfile?
|
||||||
|
|
||||||
|
//aapsLogger.debug("D: basal profile on read: " + basalProfile);
|
||||||
|
try {
|
||||||
|
if (basalProfile != null) {
|
||||||
|
val profilesByHour = basalProfile.getProfilesByHour(medtronicPumpPlugin.pumpDescription.pumpType)
|
||||||
|
if (!BasalProfile.isBasalProfileByHourUndefined(profilesByHour)) {
|
||||||
|
medtronicPumpStatus.basalsByHour = profilesByHour
|
||||||
|
medtronicPumpStatus.basalProfileStatus = BasalProfileStatus.ProfileOK
|
||||||
|
//aapsLogger.debug("D: basal profile on read: basalsByHour: " + BasalProfile.getProfilesByHourToString(medtronicPumpStatus.basalsByHour));
|
||||||
|
} else {
|
||||||
|
uiTask.responseType = MedtronicUIResponseType.Error
|
||||||
|
uiTask.errorDescription = "No profile found."
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Basal Profile was NOT valid. [%s]", basalProfile.basalProfileToStringError()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Basal Profile was returned, but was invalid. [%s]", basalProfile!!.basalProfileToStringError()))
|
||||||
|
uiTask.responseType = MedtronicUIResponseType.Error
|
||||||
|
uiTask.errorDescription = "No profile found."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetBolus -> {
|
||||||
|
medtronicPumpStatus.lastBolusAmount = uiTask.getDoubleFromParameters(0)
|
||||||
|
medtronicPumpStatus.lastBolusTime = Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetRemainingInsulin -> {
|
||||||
|
medtronicPumpStatus.reservoirRemainingUnits = uiTask.result as Double
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.CancelTBR -> {
|
||||||
|
medtronicPumpStatus.tempBasalStart = null
|
||||||
|
medtronicPumpStatus.tempBasalAmount = null
|
||||||
|
medtronicPumpStatus.tempBasalLength = null
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetRealTimeClock -> {
|
||||||
|
processTime(uiTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetRealTimeClock -> {
|
||||||
|
val response = uiTask.result as Boolean
|
||||||
|
aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "New time was %s set.", if (response) "" else "NOT"))
|
||||||
|
if (response) {
|
||||||
|
medtronicUtil.pumpTime!!.timeDifference = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetBatteryStatus -> {
|
||||||
|
val batteryStatusDTO = uiTask.result as BatteryStatusDTO?
|
||||||
|
if (batteryStatusDTO != null) {
|
||||||
|
medtronicPumpStatus.batteryRemaining = batteryStatusDTO.getCalculatedPercent(medtronicPumpStatus.batteryType)
|
||||||
|
if (batteryStatusDTO.voltage != null) {
|
||||||
|
medtronicPumpStatus.batteryVoltage = batteryStatusDTO.voltage
|
||||||
|
}
|
||||||
|
aapsLogger.debug(LTag.PUMP, String.format(Locale.ENGLISH, "BatteryStatus: %s", batteryStatusDTO.toString()))
|
||||||
|
} else {
|
||||||
|
medtronicPumpStatus.batteryVoltage = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.PumpModel -> {
|
||||||
|
if (medtronicPumpStatus.medtronicDeviceType !== medtronicUtil.medtronicPumpModel) {
|
||||||
|
aapsLogger.warn(LTag.PUMP, "Configured pump is different then pump detected !")
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpTypeNotSame, resourceHelper, rxBus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.Settings_512,
|
||||||
|
MedtronicCommandType.Settings -> {
|
||||||
|
postProcessSettings(uiTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processTime(uiTask: MedtronicUITask) {
|
||||||
|
val clockDTO = uiTask.result as ClockDTO?
|
||||||
|
if (clockDTO != null) {
|
||||||
|
val dur = Duration(clockDTO.pumpTime.toDateTime(DateTimeZone.UTC),
|
||||||
|
clockDTO.localDeviceTime.toDateTime(DateTimeZone.UTC))
|
||||||
|
clockDTO.timeDifference = dur.standardSeconds.toInt()
|
||||||
|
medtronicUtil.pumpTime = clockDTO
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Pump Time: " + clockDTO.localDeviceTime + ", DeviceTime=" + clockDTO.pumpTime + //
|
||||||
|
", diff: " + dur.standardSeconds + " s")
|
||||||
|
} else {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "Problem with returned data: " + medtronicUtil.gsonInstance.toJson(uiTask.result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postProcessSettings(uiTask: MedtronicUITask) {
|
||||||
|
val settings = uiTask.result as? Map<String, PumpSettingDTO>
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
medtronicUtil.settings = settings
|
||||||
|
var checkValue: PumpSettingDTO
|
||||||
|
medtronicPumpPlugin.rileyLinkService.verifyConfiguration()
|
||||||
|
|
||||||
|
// check profile
|
||||||
|
if (settings.containsKey("PCFG_BASAL_PROFILES_ENABLED") && settings.containsKey("PCFG_ACTIVE_BASAL_PROFILE")) {
|
||||||
|
checkValue = settings["PCFG_BASAL_PROFILES_ENABLED"]!!
|
||||||
|
if ("Yes" != checkValue.value) {
|
||||||
|
aapsLogger.error(LTag.PUMP, "Basal profiles are not enabled on pump.")
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpBasalProfilesNotEnabled, resourceHelper, rxBus)
|
||||||
|
} else {
|
||||||
|
checkValue = settings["PCFG_ACTIVE_BASAL_PROFILE"]!!
|
||||||
|
if ("STD" != checkValue.value) {
|
||||||
|
aapsLogger.error("Basal profile set on pump is incorrect (must be STD).")
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpIncorrectBasalProfileSelected, resourceHelper, rxBus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TBR
|
||||||
|
if (settings.containsKey("PCFG_TEMP_BASAL_TYPE")) {
|
||||||
|
if ("Units" != settings["PCFG_TEMP_BASAL_TYPE"]!!.value) {
|
||||||
|
aapsLogger.error("Wrong TBR type set on pump (must be Absolute).")
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongTBRTypeSet, resourceHelper, rxBus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MAXes
|
||||||
|
if (settings.containsKey("PCFG_MAX_BOLUS")) {
|
||||||
|
checkValue = settings["PCFG_MAX_BOLUS"]!!
|
||||||
|
if (!MedtronicUtil.isSame(checkValue.value.toDouble(), medtronicPumpStatus.maxBolus!!)) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Wrong Max Bolus set on Pump (current=%s, required=%.2f).", checkValue.value, medtronicPumpStatus.maxBolus))
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongMaxBolusSet, resourceHelper, rxBus, medtronicPumpStatus.maxBolus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.containsKey("PCFG_MAX_BASAL")) {
|
||||||
|
checkValue = settings["PCFG_MAX_BASAL"]!!
|
||||||
|
if (!MedtronicUtil.isSame(checkValue.value.toDouble(), medtronicPumpStatus.maxBasal!!)) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Wrong Max Basal set on Pump (current=%s, required=%.2f).", checkValue.value, medtronicPumpStatus.maxBasal))
|
||||||
|
medtronicUtil.sendNotification(MedtronicNotificationType.PumpWrongMaxBasalSet, resourceHelper, rxBus, medtronicPumpStatus.maxBasal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,234 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui;
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.android.HasAndroidInjector;
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/14/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MedtronicUITask {
|
|
||||||
|
|
||||||
@Inject RxBusWrapper rxBus;
|
|
||||||
@Inject AAPSLogger aapsLogger;
|
|
||||||
@Inject MedtronicPumpStatus medtronicPumpStatus;
|
|
||||||
@Inject MedtronicUtil medtronicUtil;
|
|
||||||
|
|
||||||
private final HasAndroidInjector injector;
|
|
||||||
|
|
||||||
public MedtronicCommandType commandType;
|
|
||||||
public Object returnData;
|
|
||||||
String errorDescription;
|
|
||||||
// boolean invalid = false;
|
|
||||||
private Object[] parameters;
|
|
||||||
// private boolean received;
|
|
||||||
MedtronicUIResponseType responseType;
|
|
||||||
|
|
||||||
|
|
||||||
public MedtronicUITask(HasAndroidInjector injector, MedtronicCommandType commandType) {
|
|
||||||
this.injector = injector;
|
|
||||||
this.injector.androidInjector().inject(this);
|
|
||||||
this.commandType = commandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MedtronicUITask(HasAndroidInjector injector, MedtronicCommandType commandType, Object... parameters) {
|
|
||||||
this.injector = injector;
|
|
||||||
this.injector.androidInjector().inject(this);
|
|
||||||
this.commandType = commandType;
|
|
||||||
this.parameters = parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void execute(MedtronicCommunicationManager communicationManager) {
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "MedtronicUITask: @@@ In execute. " + commandType);
|
|
||||||
|
|
||||||
switch (commandType) {
|
|
||||||
case PumpModel: {
|
|
||||||
returnData = communicationManager.getPumpModel();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetBasalProfileSTD: {
|
|
||||||
returnData = communicationManager.getBasalProfile();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetRemainingInsulin: {
|
|
||||||
returnData = communicationManager.getRemainingInsulin();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetRealTimeClock: {
|
|
||||||
returnData = communicationManager.getPumpTime();
|
|
||||||
medtronicUtil.setPumpTime(null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetRealTimeClock: {
|
|
||||||
returnData = communicationManager.setPumpTime();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetBatteryStatus: {
|
|
||||||
returnData = communicationManager.getRemainingBattery();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetTemporaryBasal: {
|
|
||||||
TempBasalPair tbr = getTBRSettings();
|
|
||||||
if (tbr != null) {
|
|
||||||
returnData = communicationManager.setTBR(tbr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ReadTemporaryBasal: {
|
|
||||||
returnData = communicationManager.getTemporaryBasal();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
case Settings:
|
|
||||||
case Settings_512: {
|
|
||||||
returnData = communicationManager.getPumpSettings();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetBolus: {
|
|
||||||
Double amount = getDoubleFromParameters(0);
|
|
||||||
|
|
||||||
if (amount != null)
|
|
||||||
returnData = communicationManager.setBolus(amount);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CancelTBR: {
|
|
||||||
returnData = communicationManager.cancelTBR();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SetBasalProfileSTD:
|
|
||||||
case SetBasalProfileA: {
|
|
||||||
BasalProfile profile = (BasalProfile) parameters[0];
|
|
||||||
|
|
||||||
returnData = communicationManager.setBasalProfile(profile);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GetHistoryData: {
|
|
||||||
returnData = communicationManager.getPumpHistory((PumpHistoryEntry) parameters[0],
|
|
||||||
(LocalDateTime) parameters[1]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: {
|
|
||||||
aapsLogger.warn(LTag.PUMP, String.format(Locale.ENGLISH, "This commandType is not supported (yet) - %s.", commandType));
|
|
||||||
// invalid = true;
|
|
||||||
responseType = MedtronicUIResponseType.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseType == null) {
|
|
||||||
if (returnData == null) {
|
|
||||||
errorDescription = communicationManager.getErrorResponse();
|
|
||||||
this.responseType = MedtronicUIResponseType.Error;
|
|
||||||
} else {
|
|
||||||
this.responseType = MedtronicUIResponseType.Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private TempBasalPair getTBRSettings() {
|
|
||||||
return new TempBasalPair(getDoubleFromParameters(0), //
|
|
||||||
false, //
|
|
||||||
getIntegerFromParameters(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Float getFloatFromParameters(int index) {
|
|
||||||
return (Float) parameters[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Double getDoubleFromParameters(int index) {
|
|
||||||
return (Double) parameters[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Integer getIntegerFromParameters(int index) {
|
|
||||||
return (Integer) parameters[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Object getResult() {
|
|
||||||
return returnData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isReceived() {
|
|
||||||
return (returnData != null || errorDescription != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void postProcess(MedtronicUIPostprocessor postprocessor) {
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMP, "MedtronicUITask: @@@ In execute. " + commandType);
|
|
||||||
|
|
||||||
if (responseType == MedtronicUIResponseType.Data) {
|
|
||||||
postprocessor.postProcessData(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseType == MedtronicUIResponseType.Invalid) {
|
|
||||||
rxBus.send(new EventRileyLinkDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating,
|
|
||||||
"Unsupported command in MedtronicUITask"));
|
|
||||||
} else if (responseType == MedtronicUIResponseType.Error) {
|
|
||||||
rxBus.send(new EventRileyLinkDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating,
|
|
||||||
errorDescription));
|
|
||||||
} else {
|
|
||||||
rxBus.send(new EventMedtronicPumpValuesChanged());
|
|
||||||
medtronicPumpStatus.setLastCommunicationToNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
medtronicUtil.setCurrentCommand(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean hasData() {
|
|
||||||
return (responseType == MedtronicUIResponseType.Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Object getParameter(int index) {
|
|
||||||
return parameters[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MedtronicUIResponseType getResponseType() {
|
|
||||||
return this.responseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui
|
||||||
|
|
||||||
|
import dagger.android.HasAndroidInjector
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/14/18.
|
||||||
|
*/
|
||||||
|
class MedtronicUITask {
|
||||||
|
|
||||||
|
@Inject lateinit var rxBus: RxBusWrapper
|
||||||
|
@Inject lateinit var aapsLogger: AAPSLogger
|
||||||
|
@Inject lateinit var medtronicPumpStatus: MedtronicPumpStatus
|
||||||
|
@Inject lateinit var medtronicUtil: MedtronicUtil
|
||||||
|
|
||||||
|
private val injector: HasAndroidInjector
|
||||||
|
var commandType: MedtronicCommandType
|
||||||
|
var result: Any? = null
|
||||||
|
var errorDescription: String? = null
|
||||||
|
var parameters: List<Any?>? = null
|
||||||
|
var responseType: MedtronicUIResponseType? = null
|
||||||
|
|
||||||
|
constructor(injector: HasAndroidInjector, commandType: MedtronicCommandType) {
|
||||||
|
this.injector = injector
|
||||||
|
this.injector.androidInjector().inject(this)
|
||||||
|
this.commandType = commandType
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(injector: HasAndroidInjector, commandType: MedtronicCommandType, parameters: List<Any>?) {
|
||||||
|
this.injector = injector
|
||||||
|
this.injector.androidInjector().inject(this)
|
||||||
|
this.commandType = commandType
|
||||||
|
this.parameters = parameters //as Array<Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun execute(communicationManager: MedtronicCommunicationManager) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtronicUITask: @@@ In execute. $commandType")
|
||||||
|
when (commandType) {
|
||||||
|
MedtronicCommandType.PumpModel -> {
|
||||||
|
result = communicationManager.getPumpModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetBasalProfileSTD -> {
|
||||||
|
result = communicationManager.getBasalProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetRemainingInsulin -> {
|
||||||
|
result = communicationManager.getRemainingInsulin()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetRealTimeClock -> {
|
||||||
|
result = communicationManager.getPumpTime()
|
||||||
|
//medtronicUtil.pumpTime = null
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetRealTimeClock -> {
|
||||||
|
result = communicationManager.setPumpTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetBatteryStatus -> {
|
||||||
|
result = communicationManager.getRemainingBattery()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetTemporaryBasal -> {
|
||||||
|
val tbr = getTbrSettings()
|
||||||
|
if (tbr != null) {
|
||||||
|
result = communicationManager.setTemporaryBasal(tbr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.ReadTemporaryBasal -> {
|
||||||
|
result = communicationManager.getTemporaryBasal()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.Settings, MedtronicCommandType.Settings_512 -> {
|
||||||
|
result = communicationManager.getPumpSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetBolus -> {
|
||||||
|
val amount = getDoubleFromParameters(0)
|
||||||
|
if (amount != null) result = communicationManager.setBolus(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.CancelTBR -> {
|
||||||
|
result = communicationManager.cancelTBR()
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.SetBasalProfileSTD,
|
||||||
|
MedtronicCommandType.SetBasalProfileA -> {
|
||||||
|
val profile = parameters!![0] as BasalProfile
|
||||||
|
result = communicationManager.setBasalProfile(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
MedtronicCommandType.GetHistoryData -> {
|
||||||
|
result = communicationManager.getPumpHistory(parameters!![0] as PumpHistoryEntry?,
|
||||||
|
parameters!![1] as LocalDateTime?)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
aapsLogger.warn(LTag.PUMP, String.format(Locale.ENGLISH, "This commandType is not supported (yet) - %s.", commandType))
|
||||||
|
// invalid = true;
|
||||||
|
responseType = MedtronicUIResponseType.Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (responseType == null) {
|
||||||
|
if (result == null) {
|
||||||
|
errorDescription = communicationManager.errorResponse
|
||||||
|
responseType = MedtronicUIResponseType.Error
|
||||||
|
} else {
|
||||||
|
responseType = MedtronicUIResponseType.Data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTbrSettings(): TempBasalPair? {
|
||||||
|
return TempBasalPair(getDoubleFromParameters(0)!!, //
|
||||||
|
false, //
|
||||||
|
getIntegerFromParameters(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFloatFromParameters(index: Int): Float {
|
||||||
|
return parameters!![index] as Float
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDoubleFromParameters(index: Int): Double? {
|
||||||
|
return parameters!![index] as Double?
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getIntegerFromParameters(index: Int): Int {
|
||||||
|
return parameters!![index] as Int
|
||||||
|
}
|
||||||
|
|
||||||
|
val isReceived: Boolean
|
||||||
|
get() = result != null || errorDescription != null
|
||||||
|
|
||||||
|
fun postProcess(postprocessor: MedtronicUIPostprocessor) {
|
||||||
|
aapsLogger.debug(LTag.PUMP, "MedtronicUITask: @@@ In execute. $commandType")
|
||||||
|
if (responseType === MedtronicUIResponseType.Data) {
|
||||||
|
postprocessor.postProcessData(this)
|
||||||
|
}
|
||||||
|
if (responseType === MedtronicUIResponseType.Invalid) {
|
||||||
|
rxBus.send(EventRileyLinkDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating,
|
||||||
|
"Unsupported command in MedtronicUITask"))
|
||||||
|
} else if (responseType === MedtronicUIResponseType.Error) {
|
||||||
|
rxBus.send(EventRileyLinkDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating,
|
||||||
|
errorDescription))
|
||||||
|
} else {
|
||||||
|
rxBus.send(EventMedtronicPumpValuesChanged())
|
||||||
|
medtronicPumpStatus.setLastCommunicationToNow()
|
||||||
|
}
|
||||||
|
medtronicUtil.setCurrentCommand(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasData(): Boolean {
|
||||||
|
return responseType === MedtronicUIResponseType.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getParameter(index: Int): Any? {
|
||||||
|
return parameters!![index]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,384 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import org.joda.time.Instant;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 6/1/15.
|
|
||||||
* <p>
|
|
||||||
* There are three basal profiles stored on the pump. (722 only?) They are all parsed the same, the user just has 3 to
|
|
||||||
* choose from: Standard, A, and B
|
|
||||||
* <p>
|
|
||||||
* The byte array is 48 times three byte entries long, plus a zero? If the profile is completely empty, it should have
|
|
||||||
* one entry: [0,0,0x3F]. The first entry of [0,0,0] marks the end of the used entries.
|
|
||||||
* <p>
|
|
||||||
* Each entry is assumed to span from the specified start time to the start time of the next entry, or to midnight if
|
|
||||||
* there are no more entries.
|
|
||||||
* <p>
|
|
||||||
* Individual entries are of the form [r,z,m] where r is the rate (in 0.025 U increments) z is zero (?) m is the start
|
|
||||||
* time-of-day for the basal rate period (in 30 minute increments)
|
|
||||||
*/
|
|
||||||
public class BasalProfile {
|
|
||||||
|
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
|
|
||||||
public static final int MAX_RAW_DATA_SIZE = (48 * 3) + 1;
|
|
||||||
private static final boolean DEBUG_BASALPROFILE = false;
|
|
||||||
@Expose
|
|
||||||
private byte[] mRawData; // store as byte array to make transport (via parcel) easier
|
|
||||||
private List<BasalProfileEntry> listEntries;
|
|
||||||
|
|
||||||
|
|
||||||
public BasalProfile(AAPSLogger aapsLogger) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public BasalProfile(AAPSLogger aapsLogger, byte[] data) {
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
setRawData(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// this asUINT8 should be combined with Record.asUINT8, and placed in a new util class.
|
|
||||||
private static int readUnsignedByte(byte b) {
|
|
||||||
return (b < 0) ? b + 256 : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
mRawData = new byte[MAX_RAW_DATA_SIZE];
|
|
||||||
mRawData[0] = 0;
|
|
||||||
mRawData[1] = 0;
|
|
||||||
mRawData[2] = 0x3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private boolean setRawData(byte[] data) {
|
|
||||||
if (data == null) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have just one entry through all day it looks like just length 1
|
|
||||||
if (data.length == 1) {
|
|
||||||
data = MedtronicUtil.createByteArray(data[0], (byte) 0, (byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.length == MAX_RAW_DATA_SIZE) {
|
|
||||||
mRawData = data;
|
|
||||||
} else {
|
|
||||||
int len = Math.min(MAX_RAW_DATA_SIZE, data.length);
|
|
||||||
mRawData = new byte[MAX_RAW_DATA_SIZE];
|
|
||||||
System.arraycopy(data, 0, mRawData, 0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean setRawDataFromHistory(byte[] data) {
|
|
||||||
if (data == null) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRawData = new byte[MAX_RAW_DATA_SIZE];
|
|
||||||
int item = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length - 2; i += 3) {
|
|
||||||
|
|
||||||
if ((data[i] == 0) && (data[i + 1] == 0) && (data[i + 2] == 0)) {
|
|
||||||
mRawData[i] = 0;
|
|
||||||
mRawData[i + 1] = 0;
|
|
||||||
mRawData[i + 2] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
mRawData[i] = data[i + 1];
|
|
||||||
mRawData[i + 1] = data[i + 2];
|
|
||||||
mRawData[i + 2] = data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void dumpBasalProfile() {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Basal Profile entries:");
|
|
||||||
List<BasalProfileEntry> entries = getEntries();
|
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
|
||||||
BasalProfileEntry entry = entries.get(i);
|
|
||||||
String startString = entry.startTime.toString("HH:mm");
|
|
||||||
// this doesn't work
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Entry %d, rate=%.3f (%s), start=%s (0x%02X)", i + 1, entry.rate,
|
|
||||||
ByteUtil.getHex(entry.rate_raw), startString, entry.startTime_raw));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getBasalProfileAsString() {
|
|
||||||
StringBuffer sb = new StringBuffer("Basal Profile entries:\n");
|
|
||||||
List<BasalProfileEntry> entries = getEntries();
|
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
|
||||||
BasalProfileEntry entry = entries.get(i);
|
|
||||||
String startString = entry.startTime.toString("HH:mm");
|
|
||||||
|
|
||||||
sb.append(String.format(Locale.ENGLISH, "Entry %d, rate=%.3f, start=%s\n", i + 1, entry.rate, startString));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String basalProfileToStringError() {
|
|
||||||
return "Basal Profile [rawData=" + ByteUtil.shortHexString(this.getRawData()) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String basalProfileToString() {
|
|
||||||
StringBuffer sb = new StringBuffer("Basal Profile [");
|
|
||||||
List<BasalProfileEntry> entries = getEntries();
|
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
|
||||||
BasalProfileEntry entry = entries.get(i);
|
|
||||||
String startString = entry.startTime.toString("HH:mm");
|
|
||||||
|
|
||||||
sb.append(String.format(Locale.ENGLISH, "%s=%.3f, ", startString, entry.rate));
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append("]");
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: this function must be expanded to include changes in which profile is in use.
|
|
||||||
// and changes to the profiles themselves.
|
|
||||||
public BasalProfileEntry getEntryForTime(Instant when) {
|
|
||||||
BasalProfileEntry rval = new BasalProfileEntry();
|
|
||||||
List<BasalProfileEntry> entries = getEntries();
|
|
||||||
if (entries.size() == 0) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): table is empty",
|
|
||||||
when.toDateTime().toLocalTime().toString("HH:mm")));
|
|
||||||
return rval;
|
|
||||||
}
|
|
||||||
// Log.w(TAG,"Assuming first entry");
|
|
||||||
rval = entries.get(0);
|
|
||||||
if (entries.size() == 1) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "getEntryForTime: Only one entry in profile");
|
|
||||||
return rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
int localMillis = when.toDateTime().toLocalTime().getMillisOfDay();
|
|
||||||
boolean done = false;
|
|
||||||
int i = 1;
|
|
||||||
while (!done) {
|
|
||||||
BasalProfileEntry entry = entries.get(i);
|
|
||||||
if (DEBUG_BASALPROFILE) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Comparing 'now'=%s to entry 'start time'=%s", when.toDateTime().toLocalTime()
|
|
||||||
.toString("HH:mm"), entry.startTime.toString("HH:mm")));
|
|
||||||
}
|
|
||||||
if (localMillis >= entry.startTime.getMillisOfDay()) {
|
|
||||||
rval = entry;
|
|
||||||
if (DEBUG_BASALPROFILE)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Accepted Entry");
|
|
||||||
} else {
|
|
||||||
// entry at i has later start time, keep older entry
|
|
||||||
if (DEBUG_BASALPROFILE)
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Rejected Entry");
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
if (i >= entries.size()) {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (DEBUG_BASALPROFILE) {
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): Returning entry: rate=%.3f (%s), start=%s (%d)", when
|
|
||||||
.toDateTime().toLocalTime().toString("HH:mm"), rval.rate, ByteUtil.getHex(rval.rate_raw),
|
|
||||||
rval.startTime.toString("HH:mm"), rval.startTime_raw));
|
|
||||||
}
|
|
||||||
return rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<BasalProfileEntry> getEntries() {
|
|
||||||
List<BasalProfileEntry> entries = new ArrayList<>();
|
|
||||||
|
|
||||||
if (mRawData == null || mRawData[2] == 0x3f) {
|
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Raw Data is empty.");
|
|
||||||
return entries; // an empty list
|
|
||||||
}
|
|
||||||
int r, st;
|
|
||||||
|
|
||||||
for (int i = 0; i < mRawData.length - 2; i += 3) {
|
|
||||||
|
|
||||||
if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0x3f))
|
|
||||||
break;
|
|
||||||
|
|
||||||
r = MedtronicUtil.makeUnsignedShort(mRawData[i + 1], mRawData[i]); // readUnsignedByte(mRawData[i]);
|
|
||||||
st = readUnsignedByte(mRawData[i + 2]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
entries.add(new BasalProfileEntry(aapsLogger, r, st));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "Error decoding basal profile from bytes: " + ByteUtil.shortHexString(mRawData));
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is used to prepare new profile
|
|
||||||
*
|
|
||||||
* @param entry
|
|
||||||
*/
|
|
||||||
public void addEntry(BasalProfileEntry entry) {
|
|
||||||
if (listEntries == null)
|
|
||||||
listEntries = new ArrayList<>();
|
|
||||||
|
|
||||||
listEntries.add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void generateRawDataFromEntries() {
|
|
||||||
|
|
||||||
List<Byte> outData = new ArrayList<>();
|
|
||||||
|
|
||||||
for (BasalProfileEntry profileEntry : listEntries) {
|
|
||||||
|
|
||||||
byte[] strokes = MedtronicUtil.getBasalStrokes(profileEntry.rate, true);
|
|
||||||
|
|
||||||
outData.add(profileEntry.rate_raw[0]);
|
|
||||||
outData.add(profileEntry.rate_raw[1]);
|
|
||||||
outData.add(profileEntry.startTime_raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setRawData(MedtronicUtil.createByteArray(outData));
|
|
||||||
|
|
||||||
// return this.mRawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double[] getProfilesByHour(PumpType pumpType) {
|
|
||||||
|
|
||||||
List<BasalProfileEntry> entries = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
entries = getEntries();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "=============================================================================");
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, " Error generating entries. Ex.: " + ex, ex);
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, " rawBasalValues: " + ByteUtil.shortHexString(this.getRawData()));
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM, "=============================================================================");
|
|
||||||
|
|
||||||
//FabricUtil.createEvent("MedtronicBasalProfileGetByHourError", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries == null || entries.size() == 0) {
|
|
||||||
Double[] basalByHour = new Double[24];
|
|
||||||
|
|
||||||
for (int i = 0; i < 24; i++) {
|
|
||||||
basalByHour[i] = 0.0d;
|
|
||||||
}
|
|
||||||
|
|
||||||
return basalByHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
Double[] basalByHour = new Double[24];
|
|
||||||
|
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
|
||||||
BasalProfileEntry current = entries.get(i);
|
|
||||||
|
|
||||||
int currentTime = (current.startTime_raw % 2 == 0) ? current.startTime_raw : current.startTime_raw - 1;
|
|
||||||
|
|
||||||
currentTime = (currentTime * 30) / 60;
|
|
||||||
|
|
||||||
int lastHour;
|
|
||||||
if ((i + 1) == entries.size()) {
|
|
||||||
lastHour = 24;
|
|
||||||
} else {
|
|
||||||
BasalProfileEntry basalProfileEntry = entries.get(i + 1);
|
|
||||||
|
|
||||||
int rawTime = (basalProfileEntry.startTime_raw % 2 == 0) ? basalProfileEntry.startTime_raw
|
|
||||||
: basalProfileEntry.startTime_raw - 1;
|
|
||||||
|
|
||||||
lastHour = (rawTime * 30) / 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
// System.out.println("Current time: " + currentTime + " Next Time: " + lastHour);
|
|
||||||
|
|
||||||
for (int j = currentTime; j < lastHour; j++) {
|
|
||||||
if (pumpType == null)
|
|
||||||
basalByHour[j] = current.rate;
|
|
||||||
else
|
|
||||||
basalByHour[j] = pumpType.determineCorrectBasalSize(current.rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return basalByHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String getProfilesByHourToString(Double[] data) {
|
|
||||||
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
|
|
||||||
for (Double value : data) {
|
|
||||||
stringBuilder.append(String.format("%.3f", value));
|
|
||||||
stringBuilder.append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringBuilder.toString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getRawData() {
|
|
||||||
return this.mRawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull public String toString() {
|
|
||||||
return basalProfileToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean verify(PumpType pumpType) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
getEntries();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Double[] profilesByHour = getProfilesByHour(pumpType);
|
|
||||||
|
|
||||||
for (Double aDouble : profilesByHour) {
|
|
||||||
if (aDouble > 35.0d)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,323 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import org.joda.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/1/15.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* There are three basal profiles stored on the pump. (722 only?) They are all parsed the same, the user just has 3 to
|
||||||
|
* choose from: Standard, A, and B
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The byte array is 48 times three byte entries long, plus a zero? If the profile is completely empty, it should have
|
||||||
|
* one entry: [0,0,0x3F]. The first entry of [0,0,0] marks the end of the used entries.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Each entry is assumed to span from the specified start time to the start time of the next entry, or to midnight if
|
||||||
|
* there are no more entries.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Individual entries are of the form [r,z,m] where r is the rate (in 0.025 U increments) z is zero (?) m is the start
|
||||||
|
* time-of-day for the basal rate period (in 30 minute increments)
|
||||||
|
*/
|
||||||
|
class BasalProfile {
|
||||||
|
|
||||||
|
private val aapsLogger: AAPSLogger
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
lateinit var rawData: ByteArray // store as byte array to make transport (via parcel) easier
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var listEntries: MutableList<BasalProfileEntry>? = null
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger) {
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger, data: ByteArray) {
|
||||||
|
this.aapsLogger = aapsLogger
|
||||||
|
setRawData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
rawData = byteArrayOf(0, 0, 0x3f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setRawData(data: ByteArray): Boolean {
|
||||||
|
var dataInternal: ByteArray = data
|
||||||
|
// if (dataInternal == null) {
|
||||||
|
// aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!")
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if we have just one entry through all day it looks like just length 1
|
||||||
|
if (dataInternal.size == 1) {
|
||||||
|
dataInternal = byteArrayOf(dataInternal[0], 0, 0)
|
||||||
|
}
|
||||||
|
if (dataInternal.size == MAX_RAW_DATA_SIZE) {
|
||||||
|
rawData = dataInternal
|
||||||
|
} else {
|
||||||
|
val len = Math.min(MAX_RAW_DATA_SIZE, data.size)
|
||||||
|
rawData = ByteArray(MAX_RAW_DATA_SIZE)
|
||||||
|
System.arraycopy(data, 0, rawData, 0, len)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRawDataFromHistory(data: ByteArray?): Boolean {
|
||||||
|
if (data == null) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rawData = ByteArray(MAX_RAW_DATA_SIZE)
|
||||||
|
var i = 0
|
||||||
|
while (i < data.size - 2) {
|
||||||
|
if (data[i] == 0.toByte() && data[i + 1] == 0.toByte() && data[i + 2] == 0.toByte()) {
|
||||||
|
rawData[i] = 0
|
||||||
|
rawData[i + 1] = 0
|
||||||
|
rawData[i + 2] = 0
|
||||||
|
}
|
||||||
|
rawData[i] = data[i + 1]
|
||||||
|
rawData[i + 1] = data[i + 2]
|
||||||
|
rawData[i + 2] = data[i]
|
||||||
|
i += 3
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dumpBasalProfile() {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Basal Profile entries:")
|
||||||
|
val entries = getEntries()
|
||||||
|
for (i in entries.indices) {
|
||||||
|
val entry = entries[i]
|
||||||
|
val startString = entry.startTime!!.toString("HH:mm")
|
||||||
|
// this doesn't work
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Entry %d, rate=%.3f (%s), start=%s (0x%02X)", i + 1, entry.rate,
|
||||||
|
ByteUtil.getHex(entry.rate_raw), startString, entry.startTime_raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val basalProfileAsString: String
|
||||||
|
get() {
|
||||||
|
val sb = StringBuffer("Basal Profile entries:\n")
|
||||||
|
val entries = getEntries()
|
||||||
|
for (i in entries.indices) {
|
||||||
|
val entry = entries[i]
|
||||||
|
val startString = entry.startTime!!.toString("HH:mm")
|
||||||
|
sb.append(String.format(Locale.ENGLISH, "Entry %d, rate=%.3f, start=%s\n", i + 1, entry.rate, startString))
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun basalProfileToStringError(): String {
|
||||||
|
return "Basal Profile [rawData=" + ByteUtil.shortHexString(rawData) + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun basalProfileToString(): String {
|
||||||
|
val sb = StringBuffer("Basal Profile [")
|
||||||
|
val entries = getEntries()
|
||||||
|
for (i in entries.indices) {
|
||||||
|
val entry = entries[i]
|
||||||
|
val startString = entry.startTime!!.toString("HH:mm")
|
||||||
|
sb.append(String.format(Locale.ENGLISH, "%s=%.3f, ", startString, entry.rate))
|
||||||
|
}
|
||||||
|
sb.append("]")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this function must be expanded to include changes in which profile is in use.
|
||||||
|
// and changes to the profiles themselves.
|
||||||
|
fun getEntryForTime(`when`: Instant): BasalProfileEntry {
|
||||||
|
var rval = BasalProfileEntry()
|
||||||
|
val entries = getEntries()
|
||||||
|
if (entries.size == 0) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): table is empty",
|
||||||
|
`when`.toDateTime().toLocalTime().toString("HH:mm")))
|
||||||
|
return rval
|
||||||
|
}
|
||||||
|
// Log.w(TAG,"Assuming first entry");
|
||||||
|
rval = entries[0]
|
||||||
|
if (entries.size == 1) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "getEntryForTime: Only one entry in profile")
|
||||||
|
return rval
|
||||||
|
}
|
||||||
|
val localMillis = `when`.toDateTime().toLocalTime().millisOfDay
|
||||||
|
var done = false
|
||||||
|
var i = 1
|
||||||
|
while (!done) {
|
||||||
|
val entry = entries[i]
|
||||||
|
if (DEBUG_BASALPROFILE) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Comparing 'now'=%s to entry 'start time'=%s", `when`.toDateTime().toLocalTime()
|
||||||
|
.toString("HH:mm"), entry.startTime!!.toString("HH:mm")))
|
||||||
|
}
|
||||||
|
if (localMillis >= entry.startTime!!.millisOfDay) {
|
||||||
|
rval = entry
|
||||||
|
if (DEBUG_BASALPROFILE) aapsLogger.debug(LTag.PUMPCOMM, "Accepted Entry")
|
||||||
|
} else {
|
||||||
|
// entry at i has later start time, keep older entry
|
||||||
|
if (DEBUG_BASALPROFILE) aapsLogger.debug(LTag.PUMPCOMM, "Rejected Entry")
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if (i >= entries.size) {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (DEBUG_BASALPROFILE) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): Returning entry: rate=%.3f (%s), start=%s (%d)", `when`
|
||||||
|
.toDateTime().toLocalTime().toString("HH:mm"), rval.rate, ByteUtil.getHex(rval.rate_raw),
|
||||||
|
rval.startTime!!.toString("HH:mm"), rval.startTime_raw))
|
||||||
|
}
|
||||||
|
return rval
|
||||||
|
}// readUnsignedByte(mRawData[i]);
|
||||||
|
|
||||||
|
// an empty list
|
||||||
|
fun getEntries(): List<BasalProfileEntry> {
|
||||||
|
val entries: MutableList<BasalProfileEntry> = ArrayList()
|
||||||
|
if (rawData[2] == 0x3f.toByte()) {
|
||||||
|
aapsLogger.warn(LTag.PUMPCOMM, "Raw Data is empty.")
|
||||||
|
return entries // an empty list
|
||||||
|
}
|
||||||
|
var r: Int
|
||||||
|
var st: Int
|
||||||
|
var i = 0
|
||||||
|
while (i < rawData.size - 2) {
|
||||||
|
if (rawData[i] == 0.toByte() && rawData[i + 1] == 0.toByte() && rawData[i + 2] == 0.toByte()) break
|
||||||
|
if (rawData[i] == 0.toByte() && rawData[i + 1] == 0.toByte() && rawData[i + 2] == 0x3f.toByte()) break
|
||||||
|
r = MedtronicUtil.makeUnsignedShort(rawData[i + 1].toInt(), rawData[i].toInt()) // readUnsignedByte(mRawData[i]);
|
||||||
|
st = readUnsignedByte(rawData[i + 2])
|
||||||
|
try {
|
||||||
|
entries.add(BasalProfileEntry(aapsLogger, r, st))
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "Error decoding basal profile from bytes: " + ByteUtil.shortHexString(rawData))
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
i += 3
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to prepare new profile
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
*/
|
||||||
|
fun addEntry(entry: BasalProfileEntry) {
|
||||||
|
if (listEntries == null) listEntries = ArrayList()
|
||||||
|
listEntries!!.add(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateRawDataFromEntries() {
|
||||||
|
val outData: MutableList<Byte> = ArrayList()
|
||||||
|
for (profileEntry in listEntries!!) {
|
||||||
|
//val strokes = MedtronicUtil.getBasalStrokes(profileEntry.rate, true)
|
||||||
|
outData.add(profileEntry.rate_raw[0])
|
||||||
|
outData.add(profileEntry.rate_raw[1])
|
||||||
|
outData.add(profileEntry.startTime_raw)
|
||||||
|
}
|
||||||
|
setRawData(MedtronicUtil.createByteArray(outData))
|
||||||
|
|
||||||
|
// return this.mRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProfilesByHour(pumpType: PumpType): DoubleArray {
|
||||||
|
var entriesCopy: List<BasalProfileEntry>? = null
|
||||||
|
try {
|
||||||
|
entriesCopy = getEntries()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "=============================================================================")
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, " Error generating entries. Ex.: $ex", ex)
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, " rawBasalValues: " + ByteUtil.shortHexString(rawData))
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, "=============================================================================")
|
||||||
|
//FabricUtil.createEvent("MedtronicBasalProfileGetByHourError", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
val basalByHour = DoubleArray(24)
|
||||||
|
|
||||||
|
if (entriesCopy == null || entriesCopy.size == 0) {
|
||||||
|
for (i in 0..23) {
|
||||||
|
basalByHour[i] = 0.0
|
||||||
|
}
|
||||||
|
return basalByHour
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in entriesCopy.indices) {
|
||||||
|
val current = entriesCopy[i]
|
||||||
|
var currentTime = if (current.startTime_raw % 2 == 0) current.startTime_raw.toInt() else current.startTime_raw - 1
|
||||||
|
currentTime = currentTime * 30 / 60
|
||||||
|
var lastHour: Int
|
||||||
|
lastHour = if (i + 1 == entriesCopy.size) {
|
||||||
|
24
|
||||||
|
} else {
|
||||||
|
val basalProfileEntry = entriesCopy[i + 1]
|
||||||
|
val rawTime = if (basalProfileEntry.startTime_raw % 2 == 0) basalProfileEntry.startTime_raw.toInt() else basalProfileEntry.startTime_raw - 1
|
||||||
|
rawTime * 30 / 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.out.println("Current time: " + currentTime + " Next Time: " + lastHour);
|
||||||
|
for (j in currentTime until lastHour) {
|
||||||
|
// if (pumpType == null)
|
||||||
|
// basalByHour[j] = current.rate
|
||||||
|
// else
|
||||||
|
basalByHour[j] = pumpType.determineCorrectBasalSize(current.rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return basalByHour
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return basalProfileToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verify(pumpType: PumpType): Boolean {
|
||||||
|
try {
|
||||||
|
getEntries()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val profilesByHour = getProfilesByHour(pumpType)
|
||||||
|
for (aDouble in profilesByHour) {
|
||||||
|
if (aDouble > 35.0) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val MAX_RAW_DATA_SIZE = 48 * 3 + 1
|
||||||
|
private const val DEBUG_BASALPROFILE = false
|
||||||
|
|
||||||
|
private fun readUnsignedByte(b: Byte): Int {
|
||||||
|
return if (b < 0) b + 256 else b.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getProfilesByHourToString(data: DoubleArray): String {
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
for (value in data) {
|
||||||
|
stringBuilder.append(String.format("%.3f", value))
|
||||||
|
stringBuilder.append(" ")
|
||||||
|
}
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isBasalProfileByHourUndefined(basalByHour: DoubleArray): Boolean {
|
||||||
|
for (i in 0..23) {
|
||||||
|
if (basalByHour[i] > 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import org.joda.time.LocalTime;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 6/1/15.
|
|
||||||
* This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile
|
|
||||||
* - fixed rate is not one bit but two
|
|
||||||
*/
|
|
||||||
public class BasalProfileEntry {
|
|
||||||
|
|
||||||
byte[] rate_raw;
|
|
||||||
public double rate;
|
|
||||||
byte startTime_raw;
|
|
||||||
public LocalTime startTime; // Just a "time of day"
|
|
||||||
|
|
||||||
public BasalProfileEntry() {
|
|
||||||
rate = -9.999E6;
|
|
||||||
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(0xFF, true);
|
|
||||||
startTime = new LocalTime(0);
|
|
||||||
startTime_raw = (byte) 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BasalProfileEntry(double rate, int hour, int minutes) {
|
|
||||||
byte[] data = MedtronicUtil.getBasalStrokes(rate, true);
|
|
||||||
|
|
||||||
rate_raw = new byte[2];
|
|
||||||
rate_raw[0] = data[1];
|
|
||||||
rate_raw[1] = data[0];
|
|
||||||
|
|
||||||
int interval = hour * 2;
|
|
||||||
|
|
||||||
if (minutes == 30) {
|
|
||||||
interval++;
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime_raw = (byte) interval;
|
|
||||||
startTime = new LocalTime(hour, minutes == 30 ? 30 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
BasalProfileEntry(AAPSLogger aapsLogger, int rateStrokes, int startTimeInterval) {
|
|
||||||
// rateByte is insulin delivery rate, U/hr, in 0.025 U increments
|
|
||||||
// startTimeByte is time-of-day, in 30 minute increments
|
|
||||||
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateStrokes, true);
|
|
||||||
rate = rateStrokes * 0.025;
|
|
||||||
startTime_raw = (byte) startTimeInterval;
|
|
||||||
|
|
||||||
try {
|
|
||||||
startTime = new LocalTime(startTimeInterval / 2, (startTimeInterval % 2) * 30);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
aapsLogger.error(LTag.PUMPCOMM,
|
|
||||||
String.format(Locale.ENGLISH, "Error creating BasalProfileEntry: startTimeInterval=%d, startTime_raw=%d, hours=%d, rateStrokes=%d",
|
|
||||||
startTimeInterval, startTime_raw, startTimeInterval / 2, rateStrokes));
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
BasalProfileEntry(byte rateByte, int startTimeByte) {
|
|
||||||
// rateByte is insulin delivery rate, U/hr, in 0.025 U increments
|
|
||||||
// startTimeByte is time-of-day, in 30 minute increments
|
|
||||||
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateByte, true);
|
|
||||||
rate = rateByte * 0.025;
|
|
||||||
startTime_raw = (byte) startTimeByte;
|
|
||||||
startTime = new LocalTime(startTimeByte / 2, (startTimeByte % 2) * 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStartTime(LocalTime localTime) {
|
|
||||||
this.startTime = localTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRate(double rate) {
|
|
||||||
this.rate = rate;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import org.joda.time.LocalTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 6/1/15.
|
||||||
|
* This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile
|
||||||
|
* - fixed rate is not one bit but two
|
||||||
|
*/
|
||||||
|
class BasalProfileEntry {
|
||||||
|
|
||||||
|
var rate_raw: ByteArray
|
||||||
|
var rate = 0.0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime_raw: Byte
|
||||||
|
var startTime : LocalTime? = null // Just a "time of day"
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
rate = -9.999E6
|
||||||
|
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(0xFF, true)
|
||||||
|
startTime = LocalTime(0)
|
||||||
|
startTime_raw = 0xFF.toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(rate: Double, hour: Int, minutes: Int) {
|
||||||
|
val data = MedtronicUtil.getBasalStrokes(rate, true)
|
||||||
|
rate_raw = ByteArray(2)
|
||||||
|
rate_raw[0] = data[1]
|
||||||
|
rate_raw[1] = data[0]
|
||||||
|
var interval = hour * 2
|
||||||
|
if (minutes == 30) {
|
||||||
|
interval++
|
||||||
|
}
|
||||||
|
startTime_raw = interval.toByte()
|
||||||
|
startTime = LocalTime(hour, if (minutes == 30) 30 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(aapsLogger: AAPSLogger, rateStrokes: Int, startTimeInterval: Int) {
|
||||||
|
// rateByte is insulin delivery rate, U/hr, in 0.025 U increments
|
||||||
|
// startTimeByte is time-of-day, in 30 minute increments
|
||||||
|
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateStrokes, true)
|
||||||
|
rate = rateStrokes * 0.025
|
||||||
|
startTime_raw = startTimeInterval.toByte()
|
||||||
|
startTime = try {
|
||||||
|
LocalTime(startTimeInterval / 2, startTimeInterval % 2 * 30)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error creating BasalProfileEntry: startTimeInterval=%d, startTime_raw=%d, hours=%d, rateStrokes=%d",
|
||||||
|
startTimeInterval, startTime_raw, startTimeInterval / 2, rateStrokes))
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(rateByte: Byte, startTimeByte: Int) {
|
||||||
|
// rateByte is insulin delivery rate, U/hr, in 0.025 U increments
|
||||||
|
// startTimeByte is time-of-day, in 30 minute increments
|
||||||
|
rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateByte.toInt(), true)
|
||||||
|
rate = rateByte * 0.025
|
||||||
|
startTime_raw = startTimeByte.toByte()
|
||||||
|
startTime = LocalTime(startTimeByte / 2, startTimeByte % 2 * 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/14/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class BatteryStatusDTO {
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
public BatteryStatusType batteryStatusType;
|
|
||||||
@Expose
|
|
||||||
public Double voltage;
|
|
||||||
|
|
||||||
public boolean extendedDataReceived = false;
|
|
||||||
|
|
||||||
|
|
||||||
public int getCalculatedPercent(BatteryType batteryType) {
|
|
||||||
if (voltage == null || batteryType == BatteryType.None) {
|
|
||||||
return (batteryStatusType == BatteryStatusType.Low || batteryStatusType == BatteryStatusType.Unknown) ? 18 : 70;
|
|
||||||
}
|
|
||||||
|
|
||||||
double percent = (voltage - batteryType.lowVoltage) / (batteryType.highVoltage - batteryType.lowVoltage);
|
|
||||||
|
|
||||||
int percentInt = (int) (percent * 100.0d);
|
|
||||||
|
|
||||||
if (percentInt < 0)
|
|
||||||
percentInt = 1;
|
|
||||||
|
|
||||||
if (percentInt > 100)
|
|
||||||
percentInt = 100;
|
|
||||||
|
|
||||||
return percentInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull public String toString() {
|
|
||||||
return String.format(Locale.ENGLISH, "BatteryStatusDTO [voltage=%.2f, alkaline=%d, lithium=%d, niZn=%d, nimh=%d]",
|
|
||||||
voltage == null ? 0.0f : voltage,
|
|
||||||
getCalculatedPercent(BatteryType.Alkaline),
|
|
||||||
getCalculatedPercent(BatteryType.Lithium),
|
|
||||||
getCalculatedPercent(BatteryType.NiZn),
|
|
||||||
getCalculatedPercent(BatteryType.NiMH));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public enum BatteryStatusType {
|
|
||||||
Normal,
|
|
||||||
Low,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/14/18.
|
||||||
|
*/
|
||||||
|
class BatteryStatusDTO {
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var batteryStatusType: BatteryStatusType? = null
|
||||||
|
@Expose
|
||||||
|
var voltage: Double? = null
|
||||||
|
|
||||||
|
var extendedDataReceived = false
|
||||||
|
|
||||||
|
|
||||||
|
fun getCalculatedPercent(batteryType: BatteryType): Int {
|
||||||
|
if (voltage == null || batteryType === BatteryType.None) {
|
||||||
|
return if (batteryStatusType == BatteryStatusType.Low || batteryStatusType == BatteryStatusType.Unknown) 18 else 70
|
||||||
|
}
|
||||||
|
val percent = (voltage!! - batteryType.lowVoltage) / (batteryType.highVoltage - batteryType.lowVoltage)
|
||||||
|
var percentInt = (percent * 100.0).toInt()
|
||||||
|
if (percentInt < 0) percentInt = 1
|
||||||
|
if (percentInt > 100) percentInt = 100
|
||||||
|
return percentInt
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return String.format(Locale.ENGLISH, "BatteryStatusDTO [voltage=%.2f, alkaline=%d, lithium=%d, niZn=%d, nimh=%d]",
|
||||||
|
if (voltage == null) 0.0f else voltage,
|
||||||
|
getCalculatedPercent(BatteryType.Alkaline),
|
||||||
|
getCalculatedPercent(BatteryType.Lithium),
|
||||||
|
getCalculatedPercent(BatteryType.NiZn),
|
||||||
|
getCalculatedPercent(BatteryType.NiMH))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class BatteryStatusType {
|
||||||
|
Normal,
|
||||||
|
Low,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,160 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Application: GGC - GNU Gluco Control
|
|
||||||
* Plug-in: Pump Tool (support for Pump devices)
|
|
||||||
* <p>
|
|
||||||
* See AUTHORS for copyright information.
|
|
||||||
* <p>
|
|
||||||
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
|
|
||||||
* License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
|
|
||||||
* version.
|
|
||||||
* <p>
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
* <p>
|
|
||||||
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
|
|
||||||
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
* <p>
|
|
||||||
* Filename: BolusDTO Description: Bolus DTO
|
|
||||||
* <p>
|
|
||||||
* Author: Andy {andy@atech-software.com}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class BolusDTO extends PumpTimeStampedRecord {
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
private Double requestedAmount;
|
|
||||||
@Expose
|
|
||||||
private Double deliveredAmount;
|
|
||||||
@Expose
|
|
||||||
private Double immediateAmount; // when Multiwave this is used
|
|
||||||
@Expose
|
|
||||||
private Integer duration;
|
|
||||||
@Expose
|
|
||||||
private PumpBolusType bolusType;
|
|
||||||
private Double insulinOnBoard;
|
|
||||||
|
|
||||||
|
|
||||||
public BolusDTO() {
|
|
||||||
// this.decimalPrecission = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getRequestedAmount() {
|
|
||||||
return requestedAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRequestedAmount(Double requestedAmount) {
|
|
||||||
this.requestedAmount = requestedAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getDeliveredAmount() {
|
|
||||||
return deliveredAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDeliveredAmount(Double deliveredAmount) {
|
|
||||||
this.deliveredAmount = deliveredAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Integer getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDuration(Integer duration) {
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public PumpBolusType getBolusType() {
|
|
||||||
return bolusType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setBolusType(PumpBolusType bolusType) {
|
|
||||||
this.bolusType = bolusType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getInsulinOnBoard() {
|
|
||||||
return insulinOnBoard;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setInsulinOnBoard(Double insulinOnBoard) {
|
|
||||||
this.insulinOnBoard = insulinOnBoard;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String getDurationString() {
|
|
||||||
int minutes = this.duration;
|
|
||||||
|
|
||||||
int h = minutes / 60;
|
|
||||||
|
|
||||||
minutes -= (h * 60);
|
|
||||||
|
|
||||||
return StringUtil.getLeadingZero(h, 2) + ":" + StringUtil.getLeadingZero(minutes, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
if ((bolusType == PumpBolusType.Normal) || (bolusType == PumpBolusType.Audio)) {
|
|
||||||
return getFormattedDecimal(this.deliveredAmount);
|
|
||||||
} else if (bolusType == PumpBolusType.Extended) {
|
|
||||||
return String.format("AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(this.deliveredAmount),
|
|
||||||
getDurationString());
|
|
||||||
} else {
|
|
||||||
return String.format("AMOUNT=%s;AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(this.immediateAmount),
|
|
||||||
getFormattedDecimal(this.deliveredAmount), getDurationString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDisplayableValue() {
|
|
||||||
String value = getValue();
|
|
||||||
|
|
||||||
value = value.replace("AMOUNT_SQUARE=", "Amount Square: ");
|
|
||||||
value = value.replace("AMOUNT=", "Amount: ");
|
|
||||||
value = value.replace("DURATION=", "Duration: ");
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Double getImmediateAmount() {
|
|
||||||
return immediateAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setImmediateAmount(Double immediateAmount) {
|
|
||||||
this.immediateAmount = immediateAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getFormattedDecimal(double value) {
|
|
||||||
return StringUtil.getFormatedValueUS(value, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getBolusKey() {
|
|
||||||
return "Bolus_" + this.bolusType.name();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "BolusDTO [type=" + bolusType.name() + ", " + getValue() + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application: GGC - GNU Gluco Control
|
||||||
|
* Plug-in: Pump Tool (support for Pump devices)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* See AUTHORS for copyright information.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||||
|
* License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Filename: BolusDTO Description: Bolus DTO
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Author: Andy {andy@atech-software.com}
|
||||||
|
*/
|
||||||
|
class BolusDTO constructor(atechDateTime: Long,
|
||||||
|
@Expose var requestedAmount: Double,
|
||||||
|
@Expose var deliveredAmount: Double,
|
||||||
|
@Expose var duration: Int = 0
|
||||||
|
) : PumpTimeStampedRecord(atechDateTime) {
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var immediateAmount: Double? = null // when Multiwave this is used
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
lateinit var bolusType: PumpBolusType
|
||||||
|
|
||||||
|
var insulinOnBoard: Double? = null
|
||||||
|
|
||||||
|
private val durationString: String
|
||||||
|
get() {
|
||||||
|
var minutes = duration
|
||||||
|
val h = minutes / 60
|
||||||
|
minutes -= h * 60
|
||||||
|
return StringUtil.getLeadingZero(h, 2) + ":" + StringUtil.getLeadingZero(minutes, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
val value: String
|
||||||
|
get() = if (bolusType === PumpBolusType.Normal || bolusType === PumpBolusType.Audio) {
|
||||||
|
getFormattedDecimal(deliveredAmount)
|
||||||
|
} else if (bolusType === PumpBolusType.Extended) {
|
||||||
|
String.format("AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(deliveredAmount),
|
||||||
|
durationString)
|
||||||
|
} else {
|
||||||
|
String.format("AMOUNT=%s;AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(immediateAmount!!),
|
||||||
|
getFormattedDecimal(deliveredAmount), durationString)
|
||||||
|
}
|
||||||
|
|
||||||
|
val displayableValue: String
|
||||||
|
get() {
|
||||||
|
var valueTemp = value
|
||||||
|
valueTemp = valueTemp.replace("AMOUNT_SQUARE=", "Amount Square: ")
|
||||||
|
valueTemp = valueTemp.replace("AMOUNT=", "Amount: ")
|
||||||
|
valueTemp = valueTemp.replace("DURATION=", "Duration: ")
|
||||||
|
return valueTemp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFormattedDecimal(value: Double): String {
|
||||||
|
return StringUtil.getFormatedValueUS(value, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
val bolusKey: String
|
||||||
|
get() = "Bolus_" + bolusType.name
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BolusDTO [type=" + bolusType.name + ", " + value + "]"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 18.05.15.
|
|
||||||
*/
|
|
||||||
public class BolusWizardDTO extends PumpTimeStampedRecord {
|
|
||||||
|
|
||||||
// bloodGlucose and bgTarets are in mg/dL
|
|
||||||
public Integer bloodGlucose = 0; // mg/dL
|
|
||||||
public Integer carbs = 0;
|
|
||||||
public String chUnit = "g";
|
|
||||||
|
|
||||||
public Float carbRatio = 0.0f;
|
|
||||||
public Float insulinSensitivity = 0.0f;
|
|
||||||
public Integer bgTargetLow = 0;
|
|
||||||
public Integer bgTargetHigh = 0;
|
|
||||||
public Float bolusTotal = 0.0f;
|
|
||||||
public Float correctionEstimate = 0.0f;
|
|
||||||
public Float foodEstimate = 0.0f;
|
|
||||||
public Float unabsorbedInsulin = 0.0f;
|
|
||||||
|
|
||||||
|
|
||||||
// public LocalDateTime localDateTime;
|
|
||||||
// public long atechDateTime;
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return String.format(Locale.ENGLISH, "BG=%d;CH=%d;CH_UNIT=%s;CH_INS_RATIO=%5.3f;BG_INS_RATIO=%5.3f;"
|
|
||||||
+ "BG_TARGET_LOW=%d;BG_TARGET_HIGH=%d;BOLUS_TOTAL=%5.3f;"
|
|
||||||
+ "BOLUS_CORRECTION=%5.3f;BOLUS_FOOD=%5.3f;UNABSORBED_INSULIN=%5.3f", //
|
|
||||||
bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, //
|
|
||||||
bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayableValue() {
|
|
||||||
return String.format(Locale.ENGLISH, "Bg=%d, CH=%d %s, Ch/Ins Ratio=%5.3f, Bg/Ins Ratio=%5.3f;"
|
|
||||||
+ "Bg Target(L/H)=%d/%d, Bolus: Total=%5.3f, "
|
|
||||||
+ "Correction=%5.3f, Food=%5.3f, IOB=%5.3f", //
|
|
||||||
bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, //
|
|
||||||
bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return "BolusWizardDTO [dateTime=" + DateTimeUtil.toString(atechDateTime) + ", " + getValue() + "]";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 18.05.15.
|
||||||
|
*/
|
||||||
|
class BolusWizardDTO : PumpTimeStampedRecord() {
|
||||||
|
|
||||||
|
// bloodGlucose and bgTarets are in mg/dL
|
||||||
|
var bloodGlucose = 0 // mg/dL
|
||||||
|
var carbs = 0
|
||||||
|
var chUnit = "g"
|
||||||
|
var carbRatio = 0.0f
|
||||||
|
var insulinSensitivity = 0.0f
|
||||||
|
var bgTargetLow = 0
|
||||||
|
var bgTargetHigh = 0
|
||||||
|
var bolusTotal = 0.0f
|
||||||
|
var correctionEstimate = 0.0f
|
||||||
|
var foodEstimate = 0.0f
|
||||||
|
var unabsorbedInsulin = 0.0f//
|
||||||
|
|
||||||
|
|
||||||
|
val value: String
|
||||||
|
get() = String.format(Locale.ENGLISH, "BG=%d;CH=%d;CH_UNIT=%s;CH_INS_RATIO=%5.3f;BG_INS_RATIO=%5.3f;"
|
||||||
|
+ "BG_TARGET_LOW=%d;BG_TARGET_HIGH=%d;BOLUS_TOTAL=%5.3f;"
|
||||||
|
+ "BOLUS_CORRECTION=%5.3f;BOLUS_FOOD=%5.3f;UNABSORBED_INSULIN=%5.3f", //
|
||||||
|
bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, //
|
||||||
|
bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin)
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
val displayableValue: String
|
||||||
|
get() = String.format(Locale.ENGLISH, "Bg=%d, CH=%d %s, Ch/Ins Ratio=%5.3f, Bg/Ins Ratio=%5.3f;"
|
||||||
|
+ "Bg Target(L/H)=%d/%d, Bolus: Total=%5.3f, "
|
||||||
|
+ "Correction=%5.3f, Food=%5.3f, IOB=%5.3f", //
|
||||||
|
bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, //
|
||||||
|
bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BolusWizardDTO [dateTime=" + DateTimeUtil.toString(atechDateTime) + ", " + value + "]"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 2/27/19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class ClockDTO {
|
|
||||||
|
|
||||||
public LocalDateTime localDeviceTime;
|
|
||||||
|
|
||||||
public LocalDateTime pumpTime;
|
|
||||||
|
|
||||||
public int timeDifference; // s (pump -> local)
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 2/27/19.
|
||||||
|
*/
|
||||||
|
class ClockDTO constructor(var localDeviceTime: LocalDateTime,
|
||||||
|
var pumpTime: LocalDateTime) {
|
||||||
|
// var localDeviceTime: LocalDateTime? = null
|
||||||
|
// var pumpTime: LocalDateTime? = null
|
||||||
|
var timeDifference = 0
|
||||||
|
}
|
|
@ -1,258 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.Expose;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 11/3/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE: Decoding is only done for insulin part, everything else is pretty must left undecoded.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DailyTotalsDTO {
|
|
||||||
|
|
||||||
// bg avg, bg low hi, number Bgs,
|
|
||||||
// Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0,
|
|
||||||
// Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs,
|
|
||||||
// Bolus=1.7, Fodd, Corr, Manual=1.7,
|
|
||||||
// Num bOlus=1, food/corr, Food+corr, manual bolus=1
|
|
||||||
private Double bgAvg;
|
|
||||||
private Double bgLow;
|
|
||||||
private Double bgHigh;
|
|
||||||
private Integer bgCount;
|
|
||||||
|
|
||||||
private Double sensorAvg;
|
|
||||||
private Double sensorMin;
|
|
||||||
private Double sensorMax;
|
|
||||||
private Integer sensorCalcCount;
|
|
||||||
private Integer sensorDataCount;
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
private Double insulinTotal = 0.0d;
|
|
||||||
@Expose
|
|
||||||
private Double insulinBasal = 0.0d;
|
|
||||||
@Expose
|
|
||||||
private Double insulinBolus = 0.0d;
|
|
||||||
private Double insulinCarbs;
|
|
||||||
|
|
||||||
private Double bolusTotal;
|
|
||||||
private Double bolusFood;
|
|
||||||
private Double bolusFoodAndCorr;
|
|
||||||
private Double bolusCorrection;
|
|
||||||
private Double bolusManual;
|
|
||||||
|
|
||||||
private Integer bolusCount;
|
|
||||||
private Integer bolusCountFoodOrCorr;
|
|
||||||
// Integer bolusCountCorr;
|
|
||||||
Integer bolusCountFoodAndCorr;
|
|
||||||
Integer bolusCountManual;
|
|
||||||
private Integer bolusCountFood;
|
|
||||||
private Integer bolusCountCorr;
|
|
||||||
|
|
||||||
PumpHistoryEntry entry;
|
|
||||||
|
|
||||||
|
|
||||||
public DailyTotalsDTO(PumpHistoryEntry entry) {
|
|
||||||
this.entry = entry;
|
|
||||||
|
|
||||||
switch (entry.getEntryType()) {
|
|
||||||
case EndResultTotals:
|
|
||||||
decodeEndResultsTotals(entry);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DailyTotals515:
|
|
||||||
decodeDailyTotals515(entry.getBody());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DailyTotals522:
|
|
||||||
decodeDailyTotals522(entry.getBody());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DailyTotals523:
|
|
||||||
decodeDailyTotals523(entry.getBody());
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDisplayable();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void setDisplayable() {
|
|
||||||
|
|
||||||
if (this.insulinBasal == null) {
|
|
||||||
this.entry.setDisplayableValue("Total Insulin: " + StringUtil.getFormatedValueUS(this.insulinTotal, 2));
|
|
||||||
} else {
|
|
||||||
this.entry.setDisplayableValue("Basal Insulin: " + StringUtil.getFormatedValueUS(this.insulinBasal, 2)
|
|
||||||
+ ", Total Insulin: " + StringUtil.getFormatedValueUS(this.insulinTotal, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeEndResultsTotals(PumpHistoryEntry entry) {
|
|
||||||
double totals = ByteUtil.toInt((int) entry.getHead()[0], (int) entry.getHead()[1], (int) entry.getHead()[2],
|
|
||||||
(int) entry.getHead()[3], ByteUtil.BitConversion.BIG_ENDIAN) * 0.025d;
|
|
||||||
|
|
||||||
this.insulinTotal = totals;
|
|
||||||
|
|
||||||
entry.addDecodedData("Totals", totals);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void testDecode(byte[] data) {
|
|
||||||
|
|
||||||
// Daily
|
|
||||||
|
|
||||||
byte[] body = data; // entry.getBody();
|
|
||||||
//System.out.println("Totals 522");
|
|
||||||
|
|
||||||
for (int i = 0; i < body.length - 2; i++) {
|
|
||||||
|
|
||||||
int j = ByteUtil.toInt(body[i], body[i + 1]);
|
|
||||||
int k = ByteUtil.toInt(body[i], body[i + 1], body[i + 2]);
|
|
||||||
|
|
||||||
int j1 = ByteUtil.toInt(body[i + 1], body[i]);
|
|
||||||
int k1 = ByteUtil.toInt(body[i + 2], body[i + 1], body[i]);
|
|
||||||
|
|
||||||
System.out.println(String.format(Locale.ENGLISH,
|
|
||||||
"index: %d, number=%d, del/40=%.3f, del/10=%.3f, singular=%d, sing_hex=%s", i, j, j / 40.0d, j / 10.0d,
|
|
||||||
body[i], ByteUtil.shortHexString(body[i])));
|
|
||||||
|
|
||||||
System.out.println(String.format(Locale.ENGLISH, " number[k,j1,k1]=%d / %d /%d, del/40=%.3f, del/40=%.3f, del/40=%.3f",
|
|
||||||
k, j1, k1, k / 40.0d, j1 / 40.0d, k1 / 40.0d));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeDailyTotals515(byte[] data) {
|
|
||||||
// LOG.debug("Can't decode DailyTotals515: Body={}", ByteUtil.getHex(data));
|
|
||||||
|
|
||||||
this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d;
|
|
||||||
this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d;
|
|
||||||
this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d;
|
|
||||||
|
|
||||||
// Delivery Stats: BG AVG: Bg Low/Hi=none,Number BGs=0
|
|
||||||
// Delivery Stats: INSULIN: Basal 22.30, Bolus=4.20, Catbs = 0g (26.5)
|
|
||||||
// Delivery Stats: BOLUS: Food=0.00, Corr=0.00, Manual=4.20
|
|
||||||
// Delivery Stats: NUM BOLUS: Food/Corr=0,Food+Corr=0, Manual=3
|
|
||||||
|
|
||||||
//LOG.debug("515: {}", toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeDailyTotals522(byte[] data) {
|
|
||||||
|
|
||||||
this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d;
|
|
||||||
this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d;
|
|
||||||
this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d;
|
|
||||||
|
|
||||||
this.bolusTotal = ByteUtil.toInt(data[17], data[18], data[19]) / 40.0d;
|
|
||||||
this.bolusFood = ByteUtil.toInt(data[21], data[22]) / 40.0d;
|
|
||||||
this.bolusCorrection = ByteUtil.toInt(data[23], data[24], data[25]) / 40.0d;
|
|
||||||
this.bolusManual = ByteUtil.toInt(data[26], data[27], data[28]) / 40.0d;
|
|
||||||
|
|
||||||
bolusCount = ByteUtil.asUINT8(data[30]);
|
|
||||||
bolusCountFoodOrCorr = ByteUtil.asUINT8(data[31]);
|
|
||||||
bolusCountFoodAndCorr = ByteUtil.asUINT8(data[32]);
|
|
||||||
bolusCountManual = ByteUtil.asUINT8(data[33]);
|
|
||||||
|
|
||||||
// bg avg, bg low hi, number Bgs,
|
|
||||||
// Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0,
|
|
||||||
// Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs,
|
|
||||||
// Bolus=1.7[18,19], Fodd, Corr, Manual=1.7[27,28],
|
|
||||||
// Num bOlus=1, food/corr, Food+corr, manual bolus=1
|
|
||||||
|
|
||||||
//LOG.debug("522: {}", toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void decodeDailyTotals523(byte[] data) {
|
|
||||||
|
|
||||||
this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d;
|
|
||||||
this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d;
|
|
||||||
this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d;
|
|
||||||
this.insulinCarbs = ByteUtil.toInt(data[16], data[17]) * 1.0d;
|
|
||||||
|
|
||||||
this.bolusFood = ByteUtil.toInt(data[18], data[19]) / 40.0d;
|
|
||||||
this.bolusCorrection = ByteUtil.toInt(data[20], data[21]) / 40.0d;
|
|
||||||
this.bolusFoodAndCorr = ByteUtil.toInt(data[22], data[23]) / 40.0d;
|
|
||||||
this.bolusManual = ByteUtil.toInt(data[24], data[25]) / 40.0d;
|
|
||||||
|
|
||||||
this.bolusCountFood = ByteUtil.asUINT8(data[26]);
|
|
||||||
this.bolusCountCorr = ByteUtil.asUINT8(data[27]);
|
|
||||||
this.bolusCountFoodAndCorr = ByteUtil.asUINT8(data[28]);
|
|
||||||
this.bolusCountManual = ByteUtil.asUINT8(data[29]); // +
|
|
||||||
|
|
||||||
// Delivery Stats: Carbs=11, Total Insulin=3.850, Basal=2.000
|
|
||||||
// Delivery Stats: Basal 52,Bolus 1.850, Bolus=48%o
|
|
||||||
// Delivery Stats: Food only=0.9, Food only#=1, Corr only = 0.0
|
|
||||||
// Delivery Stats: #Corr_only=0,Food+Corr=0.000, #Food+Corr=0
|
|
||||||
// Delivery Stats: Manual = 0.95, #Manual=5
|
|
||||||
|
|
||||||
//LOG.debug("523: {}", toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override @NonNull
|
|
||||||
public String toString() {
|
|
||||||
return new ToStringBuilder(this)
|
|
||||||
.append("bgAvg", bgAvg)
|
|
||||||
.append("bgLow", bgLow)
|
|
||||||
.append("bgHigh", bgHigh)
|
|
||||||
.append("bgCount", bgCount)
|
|
||||||
.append("sensorAvg", sensorAvg)
|
|
||||||
.append("sensorMin", sensorMin)
|
|
||||||
.append("sensorMax", sensorMax)
|
|
||||||
.append("sensorCalcCount", sensorCalcCount)
|
|
||||||
.append("sensorDataCount", sensorDataCount)
|
|
||||||
.append("insulinTotal", insulinTotal)
|
|
||||||
.append("insulinBasal", insulinBasal)
|
|
||||||
.append("insulinBolus", insulinBolus)
|
|
||||||
.append("insulinCarbs", insulinCarbs)
|
|
||||||
.append("bolusTotal", bolusTotal)
|
|
||||||
.append("bolusFood", bolusFood)
|
|
||||||
.append("bolusFoodAndCorr", bolusFoodAndCorr)
|
|
||||||
.append("bolusCorrection", bolusCorrection)
|
|
||||||
.append("bolusManual", bolusManual)
|
|
||||||
.append("bolusCount", bolusCount)
|
|
||||||
.append("bolusCountFoodOrCorr", bolusCountFoodOrCorr)
|
|
||||||
.append("bolusCountFoodAndCorr", bolusCountFoodAndCorr)
|
|
||||||
.append("bolusCountManual", bolusCountManual)
|
|
||||||
.append("bolusCountFood", bolusCountFood)
|
|
||||||
.append("bolusCountCorr", bolusCountCorr)
|
|
||||||
.append("entry", entry)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long timestamp() {
|
|
||||||
return DateTimeUtil.toMillisFromATD(this.entry.atechDateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double insulinBasal() {
|
|
||||||
return insulinBasal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double insulinBolus() {
|
|
||||||
return insulinBolus;
|
|
||||||
}
|
|
||||||
public double insulinTotal() {
|
|
||||||
return insulinTotal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
//import info.nightscout.androidaps.db.TDD
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntryType
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 11/3/18.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* NOTE: Decoding is only done for insulin part, everything else is pretty must left undecoded.
|
||||||
|
*/
|
||||||
|
class DailyTotalsDTO(var entry: PumpHistoryEntry) {
|
||||||
|
|
||||||
|
// bg avg, bg low hi, number Bgs,
|
||||||
|
// Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0,
|
||||||
|
// Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs,
|
||||||
|
// Bolus=1.7, Fodd, Corr, Manual=1.7,
|
||||||
|
// Num bOlus=1, food/corr, Food+corr, manual bolus=1
|
||||||
|
private val bgAvg: Double? = null
|
||||||
|
private val bgLow: Double? = null
|
||||||
|
private val bgHigh: Double? = null
|
||||||
|
private val bgCount: Int? = null
|
||||||
|
private val sensorAvg: Double? = null
|
||||||
|
private val sensorMin: Double? = null
|
||||||
|
private val sensorMax: Double? = null
|
||||||
|
private val sensorCalcCount: Int? = null
|
||||||
|
private val sensorDataCount: Int? = null
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var insulinTotal = 0.0
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var insulinBasal: Double = 0.0
|
||||||
|
|
||||||
|
@Expose
|
||||||
|
var insulinBolus = 0.0
|
||||||
|
private var insulinCarbs: Double? = null
|
||||||
|
private var bolusTotal: Double? = null
|
||||||
|
private var bolusFood: Double? = null
|
||||||
|
private var bolusFoodAndCorr: Double? = null
|
||||||
|
private var bolusCorrection: Double? = null
|
||||||
|
private var bolusManual: Double? = null
|
||||||
|
private var bolusCount: Int? = null
|
||||||
|
private var bolusCountFoodOrCorr: Int? = null
|
||||||
|
|
||||||
|
// Integer bolusCountCorr;
|
||||||
|
var bolusCountFoodAndCorr: Int? = null
|
||||||
|
var bolusCountManual: Int? = null
|
||||||
|
private var bolusCountFood: Int? = null
|
||||||
|
private var bolusCountCorr: Int? = null
|
||||||
|
private fun setDisplayable() {
|
||||||
|
if (insulinBasal == 0.0) {
|
||||||
|
entry.displayableValue = "Total Insulin: " + StringUtil.getFormatedValueUS(insulinTotal, 2)
|
||||||
|
} else {
|
||||||
|
entry.displayableValue = ("Basal Insulin: " + StringUtil.getFormatedValueUS(insulinBasal, 2)
|
||||||
|
+ ", Total Insulin: " + StringUtil.getFormatedValueUS(insulinTotal, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeEndResultsTotals(entry: PumpHistoryEntry) {
|
||||||
|
val totals = ByteUtil.toInt(entry.head[0].toInt(), entry.head[1].toInt(), entry.head[2].toInt(),
|
||||||
|
entry.head[3].toInt(), ByteUtil.BitConversion.BIG_ENDIAN) * 0.025
|
||||||
|
insulinTotal = totals
|
||||||
|
entry.addDecodedData("Totals", totals)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testDecode(data: ByteArray) {
|
||||||
|
|
||||||
|
// Daily
|
||||||
|
//System.out.println("Totals 522");
|
||||||
|
for (i in 0 until data.size - 2) {
|
||||||
|
val j = ByteUtil.toInt(data[i], data[i + 1])
|
||||||
|
val k: Int = ByteUtil.toInt(data[i], data[i + 1], data[i + 2])
|
||||||
|
val j1 = ByteUtil.toInt(data[i + 1], data[i])
|
||||||
|
val k1: Int = ByteUtil.toInt(data[i + 2], data[i + 1], data[i])
|
||||||
|
println(String.format(Locale.ENGLISH,
|
||||||
|
"index: %d, number=%d, del/40=%.3f, del/10=%.3f, singular=%d, sing_hex=%s", i, j, j / 40.0, j / 10.0,
|
||||||
|
data[i], ByteUtil.shortHexString(data[i])))
|
||||||
|
println(String.format(Locale.ENGLISH, " number[k,j1,k1]=%d / %d /%d, del/40=%.3f, del/40=%.3f, del/40=%.3f",
|
||||||
|
k, j1, k1, k / 40.0, j1 / 40.0, k1 / 40.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDailyTotals515(data: ByteArray) {
|
||||||
|
// LOG.debug("Can't decode DailyTotals515: Body={}", ByteUtil.getHex(data));
|
||||||
|
insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0
|
||||||
|
insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0
|
||||||
|
insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0
|
||||||
|
|
||||||
|
// Delivery Stats: BG AVG: Bg Low/Hi=none,Number BGs=0
|
||||||
|
// Delivery Stats: INSULIN: Basal 22.30, Bolus=4.20, Catbs = 0g (26.5)
|
||||||
|
// Delivery Stats: BOLUS: Food=0.00, Corr=0.00, Manual=4.20
|
||||||
|
// Delivery Stats: NUM BOLUS: Food/Corr=0,Food+Corr=0, Manual=3
|
||||||
|
|
||||||
|
//LOG.debug("515: {}", toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDailyTotals522(data: ByteArray) {
|
||||||
|
insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0
|
||||||
|
insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0
|
||||||
|
insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0
|
||||||
|
bolusTotal = ByteUtil.toInt(data[17], data[18], data[19]) / 40.0
|
||||||
|
bolusFood = ByteUtil.toInt(data[21], data[22]) / 40.0
|
||||||
|
bolusCorrection = ByteUtil.toInt(data[23], data[24], data[25]) / 40.0
|
||||||
|
bolusManual = ByteUtil.toInt(data[26], data[27], data[28]) / 40.0
|
||||||
|
bolusCount = ByteUtil.asUINT8(data[30])
|
||||||
|
bolusCountFoodOrCorr = ByteUtil.asUINT8(data[31])
|
||||||
|
bolusCountFoodAndCorr = ByteUtil.asUINT8(data[32])
|
||||||
|
bolusCountManual = ByteUtil.asUINT8(data[33])
|
||||||
|
|
||||||
|
// bg avg, bg low hi, number Bgs,
|
||||||
|
// Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0,
|
||||||
|
// Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs,
|
||||||
|
// Bolus=1.7[18,19], Fodd, Corr, Manual=1.7[27,28],
|
||||||
|
// Num bOlus=1, food/corr, Food+corr, manual bolus=1
|
||||||
|
|
||||||
|
//LOG.debug("522: {}", toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decodeDailyTotals523(data: ByteArray) {
|
||||||
|
insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0
|
||||||
|
insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0
|
||||||
|
insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0
|
||||||
|
insulinCarbs = ByteUtil.toInt(data[16], data[17]) * 1.0
|
||||||
|
bolusFood = ByteUtil.toInt(data[18], data[19]) / 40.0
|
||||||
|
bolusCorrection = ByteUtil.toInt(data[20], data[21]) / 40.0
|
||||||
|
bolusFoodAndCorr = ByteUtil.toInt(data[22], data[23]) / 40.0
|
||||||
|
bolusManual = ByteUtil.toInt(data[24], data[25]) / 40.0
|
||||||
|
bolusCountFood = ByteUtil.asUINT8(data[26])
|
||||||
|
bolusCountCorr = ByteUtil.asUINT8(data[27])
|
||||||
|
bolusCountFoodAndCorr = ByteUtil.asUINT8(data[28])
|
||||||
|
bolusCountManual = ByteUtil.asUINT8(data[29]) // +
|
||||||
|
|
||||||
|
// Delivery Stats: Carbs=11, Total Insulin=3.850, Basal=2.000
|
||||||
|
// Delivery Stats: Basal 52,Bolus 1.850, Bolus=48%o
|
||||||
|
// Delivery Stats: Food only=0.9, Food only#=1, Corr only = 0.0
|
||||||
|
// Delivery Stats: #Corr_only=0,Food+Corr=0.000, #Food+Corr=0
|
||||||
|
// Delivery Stats: Manual = 0.95, #Manual=5
|
||||||
|
|
||||||
|
//LOG.debug("523: {}", toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return ToStringBuilder(this)
|
||||||
|
.append("bgAvg", bgAvg)
|
||||||
|
.append("bgLow", bgLow)
|
||||||
|
.append("bgHigh", bgHigh)
|
||||||
|
.append("bgCount", bgCount)
|
||||||
|
.append("sensorAvg", sensorAvg)
|
||||||
|
.append("sensorMin", sensorMin)
|
||||||
|
.append("sensorMax", sensorMax)
|
||||||
|
.append("sensorCalcCount", sensorCalcCount)
|
||||||
|
.append("sensorDataCount", sensorDataCount)
|
||||||
|
.append("insulinTotal", insulinTotal)
|
||||||
|
.append("insulinBasal", insulinBasal)
|
||||||
|
.append("insulinBolus", insulinBolus)
|
||||||
|
.append("insulinCarbs", insulinCarbs)
|
||||||
|
.append("bolusTotal", bolusTotal)
|
||||||
|
.append("bolusFood", bolusFood)
|
||||||
|
.append("bolusFoodAndCorr", bolusFoodAndCorr)
|
||||||
|
.append("bolusCorrection", bolusCorrection)
|
||||||
|
.append("bolusManual", bolusManual)
|
||||||
|
.append("bolusCount", bolusCount)
|
||||||
|
.append("bolusCountFoodOrCorr", bolusCountFoodOrCorr)
|
||||||
|
.append("bolusCountFoodAndCorr", bolusCountFoodAndCorr)
|
||||||
|
.append("bolusCountManual", bolusCountManual)
|
||||||
|
.append("bolusCountFood", bolusCountFood)
|
||||||
|
.append("bolusCountCorr", bolusCountCorr)
|
||||||
|
.append("entry", entry)
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
when (entry.entryType) {
|
||||||
|
PumpHistoryEntryType.EndResultTotals -> decodeEndResultsTotals(entry)
|
||||||
|
PumpHistoryEntryType.DailyTotals515 -> decodeDailyTotals515(entry.body)
|
||||||
|
PumpHistoryEntryType.DailyTotals522 -> decodeDailyTotals522(entry.body)
|
||||||
|
PumpHistoryEntryType.DailyTotals523 -> decodeDailyTotals523(entry.body)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setDisplayable()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/6/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PumpSettingDTO {
|
|
||||||
|
|
||||||
public String key;
|
|
||||||
public String value;
|
|
||||||
|
|
||||||
PumpConfigurationGroup configurationGroup;
|
|
||||||
|
|
||||||
|
|
||||||
public PumpSettingDTO(String key, String value, PumpConfigurationGroup configurationGroup) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = value;
|
|
||||||
this.configurationGroup = configurationGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PumpSettingDTO [key=" + key + ",value=" + value + ",group=" + configurationGroup.name() + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/6/18.
|
||||||
|
*/
|
||||||
|
class PumpSettingDTO(var key: String, var value: String, var configurationGroup: PumpConfigurationGroup) {
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "PumpSettingDTO [key=" + key + ",value=" + value + ",group=" + configurationGroup.name + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/2/18.
|
|
||||||
*/
|
|
||||||
public class PumpTimeStampedRecord {
|
|
||||||
|
|
||||||
protected int decimalPrecission = 2;
|
|
||||||
public long atechDateTime;
|
|
||||||
|
|
||||||
|
|
||||||
public long getAtechDateTime() {
|
|
||||||
return this.atechDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setAtechDateTime(long atechDateTime) {
|
|
||||||
this.atechDateTime = atechDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getFormattedDecimal(double value) {
|
|
||||||
return StringUtil.getFormatedValueUS(value, this.decimalPrecission);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/2/18.
|
||||||
|
*/
|
||||||
|
open class PumpTimeStampedRecord constructor(var atechDateTime: Long = 0) {
|
||||||
|
|
||||||
|
var decimalPrecission = 2
|
||||||
|
// var atechDateTime: Long = 0
|
||||||
|
|
||||||
|
open fun getFormattedDecimal(value: Double): String? {
|
||||||
|
return StringUtil.getFormatedValueUS(value, decimalPrecission)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
|
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
|
||||||
|
|
||||||
public class RLHistoryItemMedtronic extends RLHistoryItem {
|
|
||||||
|
|
||||||
private final MedtronicCommandType medtronicCommandType;
|
|
||||||
|
|
||||||
public RLHistoryItemMedtronic(MedtronicCommandType medtronicCommandType) {
|
|
||||||
super(new LocalDateTime(), RLHistoryItemSource.MedtronicCommand, RileyLinkTargetDevice.MedtronicPump);
|
|
||||||
this.medtronicCommandType = medtronicCommandType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDescription(ResourceHelper resourceHelper) {
|
|
||||||
if (RLHistoryItemSource.MedtronicCommand.equals(source)) {
|
|
||||||
return medtronicCommandType.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.getDescription(resourceHelper);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
|
||||||
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
|
import org.joda.time.LocalDateTime
|
||||||
|
|
||||||
|
class RLHistoryItemMedtronic(private val medtronicCommandType: MedtronicCommandType) :
|
||||||
|
RLHistoryItem(LocalDateTime(), RLHistoryItemSource.MedtronicCommand, RileyLinkTargetDevice.MedtronicPump) {
|
||||||
|
|
||||||
|
override fun getDescription(resourceHelper: ResourceHelper): String {
|
||||||
|
return if (RLHistoryItemSource.MedtronicCommand == source) {
|
||||||
|
medtronicCommandType.name
|
||||||
|
} else super.getDescription(resourceHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,145 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by geoff on 5/29/15.
|
|
||||||
* <p>
|
|
||||||
* Just need a class to keep the pair together, for parcel transport.
|
|
||||||
*/
|
|
||||||
public class TempBasalPair extends info.nightscout.androidaps.plugins.pump.common.defs.TempBasalPair {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This constructor is for use with PumpHistoryDecoder
|
|
||||||
*
|
|
||||||
* @param rateByte
|
|
||||||
* @param startTimeByte
|
|
||||||
* @param isPercent
|
|
||||||
*/
|
|
||||||
public TempBasalPair(byte rateByte, int startTimeByte, boolean isPercent) {
|
|
||||||
super();
|
|
||||||
int rateInt = ByteUtil.asUINT8(rateByte);
|
|
||||||
|
|
||||||
if (isPercent)
|
|
||||||
this.setInsulinRate(rateByte);
|
|
||||||
else
|
|
||||||
this.setInsulinRate(rateInt * 0.025);
|
|
||||||
this.setDurationMinutes(startTimeByte * 30);
|
|
||||||
this.setPercent(isPercent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This constructor is for use with PumpHistoryDecoder
|
|
||||||
*
|
|
||||||
* @param rateByte0
|
|
||||||
* @param startTimeByte
|
|
||||||
* @param isPercent
|
|
||||||
*/
|
|
||||||
public TempBasalPair(byte rateByte0, byte rateByte1, int startTimeByte, boolean isPercent) {
|
|
||||||
if (isPercent) {
|
|
||||||
this.setInsulinRate(rateByte0);
|
|
||||||
} else {
|
|
||||||
this.setInsulinRate(ByteUtil.toInt(rateByte1, rateByte0) * 0.025);
|
|
||||||
}
|
|
||||||
this.setDurationMinutes(startTimeByte * 30);
|
|
||||||
this.setPercent(isPercent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TempBasalPair(AAPSLogger aapsLogger, byte[] response) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Received TempBasal response: " + ByteUtil.getHex(response));
|
|
||||||
|
|
||||||
setPercent(response[0] == 1);
|
|
||||||
|
|
||||||
if (isPercent()) {
|
|
||||||
setInsulinRate(response[1]);
|
|
||||||
} else {
|
|
||||||
int strokes = MedtronicUtil.makeUnsignedShort(response[2], response[3]);
|
|
||||||
|
|
||||||
setInsulinRate(strokes / 40.0d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.length < 6) {
|
|
||||||
setDurationMinutes(ByteUtil.asUINT8(response[4]));
|
|
||||||
} else {
|
|
||||||
setDurationMinutes(MedtronicUtil.makeUnsignedShort(response[4], response[5]));
|
|
||||||
}
|
|
||||||
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "TempBasalPair (with %d byte response): %s", response.length, toString()));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) {
|
|
||||||
super(insulinRate, isPercent, durationMinutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getAsRawData() {
|
|
||||||
|
|
||||||
List<Byte> list = new ArrayList<>();
|
|
||||||
|
|
||||||
list.add((byte) 5);
|
|
||||||
|
|
||||||
byte[] insulinRate = MedtronicUtil.getBasalStrokes(this.getInsulinRate(), true);
|
|
||||||
byte timeMin = (byte) MedtronicUtil.getIntervalFromMinutes(getDurationMinutes());
|
|
||||||
|
|
||||||
// list.add((byte) 0); // ?
|
|
||||||
|
|
||||||
// list.add((byte) 0); // is_absolute
|
|
||||||
|
|
||||||
if (insulinRate.length == 1)
|
|
||||||
list.add((byte) 0x00);
|
|
||||||
else
|
|
||||||
list.add(insulinRate[0]);
|
|
||||||
|
|
||||||
list.add(insulinRate[1]);
|
|
||||||
// list.add((byte) 0); // percent amount
|
|
||||||
|
|
||||||
list.add(timeMin); // 3 (time) - OK
|
|
||||||
|
|
||||||
if (insulinRate.length == 1)
|
|
||||||
list.add((byte) 0x00);
|
|
||||||
else
|
|
||||||
list.add(insulinRate[0]);
|
|
||||||
|
|
||||||
list.add(insulinRate[1]);
|
|
||||||
|
|
||||||
return MedtronicUtil.createByteArray(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCancelTBR() {
|
|
||||||
return (MedtronicUtil.isSame(getInsulinRate(), 0.0d) && getDurationMinutes() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
if (isCancelTBR()) {
|
|
||||||
return "Cancel TBR";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPercent()) {
|
|
||||||
return String.format(Locale.ENGLISH, "Rate: %.0f%%, Duration: %d min", getInsulinRate(), getDurationMinutes());
|
|
||||||
} else {
|
|
||||||
return String.format(Locale.ENGLISH, "Rate: %.3f U, Duration: %d min", getInsulinRate(), getDurationMinutes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull @Override
|
|
||||||
public String toString() {
|
|
||||||
return "TempBasalPair [" + "Rate=" + getInsulinRate() + ", DurationMinutes=" + getDurationMinutes() + ", IsPercent="
|
|
||||||
+ isPercent() + "]";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.defs.TempBasalPair
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by geoff on 5/29/15.
|
||||||
|
*
|
||||||
|
* Just need a class to keep the pair together, for parcel transport.
|
||||||
|
*/
|
||||||
|
class TempBasalPair : TempBasalPair {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is for use with PumpHistoryDecoder
|
||||||
|
*
|
||||||
|
* @param rateByte
|
||||||
|
* @param startTimeByte
|
||||||
|
* @param isPercent
|
||||||
|
*/
|
||||||
|
constructor(rateByte: Byte, startTimeByte: Int, isPercent: Boolean) : super() {
|
||||||
|
val rateInt = ByteUtil.asUINT8(rateByte)
|
||||||
|
if (isPercent) insulinRate = rateByte.toDouble() else insulinRate = rateInt * 0.025
|
||||||
|
durationMinutes = startTimeByte * 30
|
||||||
|
this.isPercent = isPercent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is for use with PumpHistoryDecoder
|
||||||
|
*
|
||||||
|
* @param rateByte0
|
||||||
|
* @param startTimeByte
|
||||||
|
* @param isPercent
|
||||||
|
*/
|
||||||
|
constructor(rateByte0: Byte, rateByte1: Byte, startTimeByte: Int, isPercent: Boolean) {
|
||||||
|
if (isPercent) {
|
||||||
|
insulinRate = rateByte0.toDouble()
|
||||||
|
} else {
|
||||||
|
insulinRate = ByteUtil.toInt(rateByte1.toInt(), rateByte0.toInt()) * 0.025
|
||||||
|
}
|
||||||
|
durationMinutes = startTimeByte * 30
|
||||||
|
this.isPercent = isPercent
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(aapsLogger: AAPSLogger, response: ByteArray) : super() {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Received TempBasal response: " + ByteUtil.getHex(response))
|
||||||
|
isPercent = response[0] == 1.toByte()
|
||||||
|
insulinRate = if (isPercent) {
|
||||||
|
response[1].toDouble()
|
||||||
|
} else {
|
||||||
|
val strokes = MedtronicUtil.makeUnsignedShort(response[2].toInt(), response[3].toInt())
|
||||||
|
strokes / 40.0
|
||||||
|
}
|
||||||
|
durationMinutes = if (response.size < 6) {
|
||||||
|
ByteUtil.asUINT8(response[4])
|
||||||
|
} else {
|
||||||
|
MedtronicUtil.makeUnsignedShort(response[4].toInt(), response[5].toInt())
|
||||||
|
}
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, "TempBasalPair (with %d byte response): %s", response.size, toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(insulinRate: Double, isPercent: Boolean, durationMinutes: Int) : super(insulinRate, isPercent, durationMinutes) {}
|
||||||
|
|
||||||
|
// list.add((byte) 0); // ?
|
||||||
|
|
||||||
|
// list.add((byte) 0); // is_absolute
|
||||||
|
// list.add((byte) 0); // percent amount
|
||||||
|
// 3 (time) - OK
|
||||||
|
val asRawData: ByteArray
|
||||||
|
get() {
|
||||||
|
val list: MutableList<Byte> = ArrayList()
|
||||||
|
list.add(5.toByte())
|
||||||
|
val insulinRate = MedtronicUtil.getBasalStrokes(insulinRate, true)
|
||||||
|
val timeMin = MedtronicUtil.getIntervalFromMinutes(durationMinutes).toByte()
|
||||||
|
|
||||||
|
// list.add((byte) 0); // ?
|
||||||
|
|
||||||
|
// list.add((byte) 0); // is_absolute
|
||||||
|
if (insulinRate.size == 1) list.add(0x00.toByte()) else list.add(insulinRate[0])
|
||||||
|
list.add(insulinRate[1])
|
||||||
|
// list.add((byte) 0); // percent amount
|
||||||
|
list.add(timeMin) // 3 (time) - OK
|
||||||
|
if (insulinRate.size == 1) list.add(0x00.toByte()) else list.add(insulinRate[0])
|
||||||
|
list.add(insulinRate[1])
|
||||||
|
return MedtronicUtil.createByteArray(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isCancelTBR: Boolean
|
||||||
|
get() = MedtronicUtil.isSame(insulinRate, 0.0) && durationMinutes == 0
|
||||||
|
|
||||||
|
val description: String
|
||||||
|
get() {
|
||||||
|
if (isCancelTBR) {
|
||||||
|
return "Cancel TBR"
|
||||||
|
}
|
||||||
|
return if (isPercent) {
|
||||||
|
String.format(Locale.ENGLISH, "Rate: %.0f%%, Duration: %d min", insulinRate, durationMinutes)
|
||||||
|
} else {
|
||||||
|
String.format(Locale.ENGLISH, "Rate: %.3f U, Duration: %d min", insulinRate, durationMinutes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return ("TempBasalPair [" + "Rate=" + insulinRate + ", DurationMinutes=" + durationMinutes + ", IsPercent="
|
||||||
|
+ isPercent + "]")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry;
|
|
||||||
|
|
||||||
public class TempBasalProcessDTO {
|
|
||||||
|
|
||||||
public PumpHistoryEntry itemOne;
|
|
||||||
public PumpHistoryEntry itemTwo;
|
|
||||||
|
|
||||||
public Operation processOperation = Operation.None;
|
|
||||||
|
|
||||||
public int getDuration() {
|
|
||||||
if (itemTwo == null) {
|
|
||||||
TempBasalPair tbr = (TempBasalPair) itemOne.getDecodedDataEntry("Object");
|
|
||||||
return tbr.getDurationMinutes();
|
|
||||||
} else {
|
|
||||||
int difference = DateTimeUtil.getATechDateDiferenceAsMinutes(itemOne.atechDateTime, itemTwo.atechDateTime);
|
|
||||||
return difference;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public enum Operation {
|
|
||||||
None,
|
|
||||||
Add,
|
|
||||||
Edit
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.data.dto
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry
|
||||||
|
|
||||||
|
class TempBasalProcessDTO constructor(var itemOne: PumpHistoryEntry,
|
||||||
|
var itemTwo: PumpHistoryEntry? = null,
|
||||||
|
var processOperation: Operation = Operation.None) {
|
||||||
|
var cancelPresent: Boolean = false
|
||||||
|
|
||||||
|
val atechDateTime: Long
|
||||||
|
get() = itemOne.atechDateTime
|
||||||
|
|
||||||
|
val pumpId: Long
|
||||||
|
get() = itemOne.pumpId
|
||||||
|
|
||||||
|
val duration: Int
|
||||||
|
get() = if (itemTwo == null) {
|
||||||
|
val tbr = itemOne.getDecodedDataEntry("Object") as TempBasalPair
|
||||||
|
tbr.durationMinutes
|
||||||
|
} else {
|
||||||
|
DateTimeUtil.getATechDateDiferenceAsMinutes(itemOne.atechDateTime, itemTwo!!.atechDateTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Operation {
|
||||||
|
None, Add, Edit
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.defs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 1/20/19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum BasalProfileStatus {
|
|
||||||
|
|
||||||
NotInitialized, //
|
|
||||||
ProfileOK, //
|
|
||||||
ProfileChanged, //
|
|
||||||
;
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.defs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 1/20/19.
|
||||||
|
*/
|
||||||
|
enum class BasalProfileStatus {
|
||||||
|
|
||||||
|
NotInitialized, //
|
||||||
|
ProfileOK, //
|
||||||
|
ProfileChanged
|
||||||
|
//
|
||||||
|
}
|
|
@ -1,34 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.defs;
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.medtronic.R;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 6/4/18.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum BatteryType {
|
|
||||||
|
|
||||||
None(R.string.key_medtronic_pump_battery_no, 0, 0),
|
|
||||||
Alkaline(R.string.key_medtronic_pump_battery_alkaline, 1.20d, 1.47d), //
|
|
||||||
Lithium(R.string.key_medtronic_pump_battery_lithium, 1.22d, 1.64d), //
|
|
||||||
NiZn(R.string.key_medtronic_pump_battery_nizn, 1.40d, 1.70d), //
|
|
||||||
NiMH(R.string.key_medtronic_pump_battery_nimh, 1.10d, 1.40d) //
|
|
||||||
;
|
|
||||||
|
|
||||||
public final @StringRes int description;
|
|
||||||
public final double lowVoltage;
|
|
||||||
public final double highVoltage;
|
|
||||||
|
|
||||||
|
|
||||||
BatteryType(int resId, double lowVoltage, double highVoltage) {
|
|
||||||
this.description = resId;
|
|
||||||
this.lowVoltage = lowVoltage;
|
|
||||||
this.highVoltage = highVoltage;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.medtronic.defs
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import info.nightscout.androidaps.plugins.pump.medtronic.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/4/18.
|
||||||
|
*/
|
||||||
|
enum class BatteryType(@field:StringRes val description: Int, val lowVoltage: Double, val highVoltage: Double) {
|
||||||
|
|
||||||
|
None(R.string.key_medtronic_pump_battery_no, 0.0, 0.0),
|
||||||
|
Alkaline(R.string.key_medtronic_pump_battery_alkaline, 1.20, 1.47), //
|
||||||
|
Lithium(R.string.key_medtronic_pump_battery_lithium, 1.22, 1.64), //
|
||||||
|
NiZn(R.string.key_medtronic_pump_battery_nizn, 1.40, 1.70), //
|
||||||
|
NiMH(R.string.key_medtronic_pump_battery_nimh, 1.10, 1.40 //
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
|
@ -1,33 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.medtronic.defs;
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by andy on 4/5/19.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public enum CommandValueDefinitionMDTType implements CommandValueDefinitionType {
|
|
||||||
GetModel, //
|
|
||||||
TuneUp, //
|
|
||||||
GetProfile, //
|
|
||||||
GetTBR, //
|
|
||||||
;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return this.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDescription() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String commandAction() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue