Merge pull request #531 from andyrozman/meallink-mdt

Medtronic driver changes - Kotlin and PumpSync
This commit is contained in:
Milos Kozak 2021-05-27 23:23:03 +02:00 committed by GitHub
commit 66a70fcd22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
174 changed files with 9872 additions and 13226 deletions

View file

@ -111,7 +111,7 @@ android {
defaultConfig {
multiDexEnabled true
versionCode 1500
version "2.8.2.1-dev-e4"
version "2.8.2.1-dev-e5"
buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"'
buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"'
@ -186,6 +186,7 @@ dependencies {
implementation project(':danars')
implementation project(':danar')
implementation project(':insight')
implementation project(':pump-common')
implementation project(':rileylink')
implementation project(':medtronic')
implementation project(':omnipod-common')
@ -197,8 +198,6 @@ dependencies {
/* Dagger2 - We are going to use dagger.android which includes
* support for Activity and fragment injection so we need to include
* the following dependencies */
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

View file

@ -8,13 +8,14 @@ import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.automation.di.AutomationModule
import info.nightscout.androidaps.combo.di.ComboModule
import info.nightscout.androidaps.dana.di.DanaHistoryModule
import info.nightscout.androidaps.di.CoreModule
import info.nightscout.androidaps.dana.di.DanaModule
import info.nightscout.androidaps.danar.di.DanaRModule
import info.nightscout.androidaps.danars.di.DanaRSModule
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.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.medtronic.di.MedtronicModule
import info.nightscout.androidaps.plugins.pump.omnipod.eros.dagger.OmnipodErosModule
@ -36,6 +37,7 @@ import javax.inject.Singleton
CommandQueueModule::class,
ObjectivesModule::class,
WizardModule::class,
PumpCommonModule::class,
RileyLinkModule::class,
MedtronicModule::class,
OmnipodErosModule::class,

View file

@ -72,21 +72,38 @@ open class AppModule {
@Module
interface AppBindings {
@Binds fun bindContext(mainApp: MainApp): Context
@Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector
@Binds fun bindActivePluginProvider(pluginStore: PluginStore): ActivePlugin
@Binds fun bindCommandQueueProvider(commandQueue: CommandQueue): CommandQueueProvider
@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 bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@Binds fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider
@Binds
fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
@Binds
fun bindNotificationHolderInterface(notificationHolder: NotificationHolderImpl): NotificationHolder
@Binds
fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefsImpl): ImportExportPrefs
@Binds
fun bindIconsProviderInterface(iconsProvider: IconsProviderImplementation): IconsProvider
@Binds fun bindLoopInterface(loopPlugin: LoopPlugin): LoopInterface
@Binds fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
@Binds fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
@Binds
fun bindIobCobCalculatorInterface(iobCobCalculatorPlugin: IobCobCalculatorPlugin): IobCobCalculator
@Binds
fun bindSmsCommunicatorInterface(smsCommunicatorPlugin: SmsCommunicatorPlugin): SmsCommunicator
@Binds
fun bindDataSyncSelector(dataSyncSelectorImplementation: DataSyncSelectorImplementation): DataSyncSelector
@Binds fun bindPumpSync(pumpSyncImplementation: PumpSyncImplementation): PumpSync
}

View file

@ -184,4 +184,4 @@ class PluginStore @Inject constructor(
override fun getPluginsList(): ArrayList<PluginBase> = ArrayList(plugins)
}
}

View file

@ -1,7 +0,0 @@
package info.nightscout.androidaps.db
interface DbObjectBase {
val date: Long
val pumpId: Long
}

View file

@ -9,7 +9,7 @@ import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
* Created by andy on 30.11.2019.
*/
@DatabaseTable(tableName = "PodHistory")
public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHistoryRecord> {
public class OmnipodHistoryRecord implements Comparable<OmnipodHistoryRecord> {
@DatabaseField(id = true)
public long date;
@ -42,7 +42,6 @@ public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHis
generatePumpId();
}
@Override
public long getDate() {
return this.date;
}
@ -91,7 +90,6 @@ public class OmnipodHistoryRecord implements DbObjectBase, Comparable<OmnipodHis
this.successConfirmed = successConfirmed;
}
@Override
public long getPumpId() {
return pumpId;
}

View file

@ -23,7 +23,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP;
@Deprecated
@DatabaseTable(tableName = "TemporaryBasals")
public class TemporaryBasal implements Interval, DbObjectBase {
public class TemporaryBasal implements Interval {
@Inject public AAPSLogger aapsLogger;
@Inject public ProfileFunction profileFunction;
@ -360,12 +360,10 @@ public class TemporaryBasal implements Interval, DbObjectBase {
}
}
@Override
public long getDate() {
return this.date;
}
@Override
public long getPumpId() {
return this.pumpId;
}

View file

@ -18,10 +18,10 @@ import javax.inject.Inject;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.core.R;
import info.nightscout.androidaps.interfaces.Profile;
import info.nightscout.androidaps.database.entities.Bolus;
import info.nightscout.androidaps.interfaces.ActivePlugin;
import info.nightscout.androidaps.interfaces.Insulin;
import info.nightscout.androidaps.interfaces.Profile;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface;
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries;
@ -33,7 +33,7 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper;
@Deprecated
@DatabaseTable(tableName = Treatment.TABLE_TREATMENTS)
public class Treatment implements DataPointWithLabelInterface, DbObjectBase {
public class Treatment implements DataPointWithLabelInterface {
@Inject public DefaultValueHelper defaultValueHelper;
@Inject public ResourceHelper resourceHelper;
@Inject public ProfileFunction profileFunction;
@ -270,12 +270,10 @@ public class Treatment implements DataPointWithLabelInterface, DbObjectBase {
// ----------------- DataPointInterface end --------------------
@Override
public long getDate() {
return this.date;
}
@Override
public long getPumpId() {
return this.pumpId;
}

View file

@ -1,5 +1,5 @@
package info.nightscout.androidaps.plugins.general.actions.defs
interface CustomActionType {
val key: String?
fun getKey(): String
}

View file

@ -320,32 +320,6 @@
<string name="timedetection">Time detection</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>
<!-- Maintenance -->

View file

@ -18,6 +18,7 @@ dependencies {
androidTestImplementation "androidx.test:rules:$androidx_rules"
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
testImplementation 'org.mockito:mockito-inline:2.8.47'
}
android {

View file

@ -16,5 +16,6 @@ android {
dependencies {
implementation project(':core')
implementation project(':pump-common')
implementation project(':rileylink')
}

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -12,7 +12,6 @@ import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.events.EventExtendedBolusChange
import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.events.EventTempBasalChange
import info.nightscout.androidaps.extensions.toStringFull
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.PumpSync
@ -99,7 +98,7 @@ class MedtronicFragment : DaggerFragment() {
binding.pumpStatusIcon.text = "{fa-bed}"
binding.history.setOnClickListener {
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() == true) {
startActivity(Intent(context, MedtronicHistoryActivity::class.java))
} else {
displayNotConfiguredDialog()
@ -107,7 +106,7 @@ class MedtronicFragment : DaggerFragment() {
}
binding.refresh.setOnClickListener {
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() != true) {
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() != true) {
displayNotConfiguredDialog()
} else {
binding.refresh.isEnabled = false
@ -121,7 +120,7 @@ class MedtronicFragment : DaggerFragment() {
}
binding.stats.setOnClickListener {
if (medtronicPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
if (medtronicPumpPlugin.rileyLinkService.verifyConfiguration() == true) {
startActivity(Intent(context, RileyLinkStatusActivity::class.java))
} else {
displayNotConfiguredDialog()
@ -161,7 +160,7 @@ class MedtronicFragment : DaggerFragment() {
.observeOn(aapsSchedulers.main)
.subscribe({
aapsLogger.debug(LTag.PUMP, "EventMedtronicPumpConfigurationChanged triggered")
medtronicPumpPlugin.rileyLinkService?.verifyConfiguration()
medtronicPumpPlugin.rileyLinkService.verifyConfiguration()
updateGUI()
}, fabricPrivacy::logException)
disposable += rxBus
@ -193,7 +192,7 @@ class MedtronicFragment : DaggerFragment() {
@Synchronized
private fun setDeviceStatus() {
val resourceId = rileyLinkServiceData.rileyLinkServiceState.resourceId
val rileyLinkError = medtronicPumpPlugin.rileyLinkService?.error
val rileyLinkError = medtronicPumpPlugin.rileyLinkService.error
binding.rlStatus.text =
when {
rileyLinkServiceData.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> resourceHelper.gs(resourceId)
@ -210,35 +209,38 @@ class MedtronicFragment : DaggerFragment() {
} ?: "-"
when (medtronicPumpStatus.pumpDeviceState) {
null,
PumpDeviceState.Sleeping -> binding.pumpStatusIcon.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
PumpDeviceState.Sleeping ->
binding.pumpStatusIcon.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
PumpDeviceState.NeverContacted,
PumpDeviceState.WakingUp,
PumpDeviceState.PumpUnreachable,
PumpDeviceState.ErrorWhenCommunicating,
PumpDeviceState.TimeoutWhenCommunicating,
PumpDeviceState.InvalidConfiguration -> binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
PumpDeviceState.InvalidConfiguration ->
binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
PumpDeviceState.Active -> {
val cmd = medtronicUtil.currentCommand
val cmd = medtronicUtil.getCurrentCommand()
if (cmd == null)
binding.pumpStatusIcon.text = " " + resourceHelper.gs(medtronicPumpStatus.pumpDeviceState.resourceId)
else {
aapsLogger.debug(LTag.PUMP, "Command: $cmd")
val cmdResourceId = cmd.resourceId
val cmdResourceId = cmd.resourceId //!!
if (cmd == MedtronicCommandType.GetHistoryData) {
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)
} else {
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()
@ -298,26 +300,31 @@ class MedtronicFragment : DaggerFragment() {
val bolus = medtronicPumpStatus.lastBolusAmount
val bolusTime = medtronicPumpStatus.lastBolusTime
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 unit = resourceHelper.gs(R.string.insulin_unit_shortname)
val ago = when {
agoMsc < 60 * 1000 -> resourceHelper.gs(R.string.medtronic_pump_connected_now)
bolusMinAgo < 60 -> dateUtil.minAgo(resourceHelper, medtronicPumpStatus.lastBolusTime.time)
else -> dateUtil.hourAgo(medtronicPumpStatus.lastBolusTime.time, resourceHelper)
bolusMinAgo < 60 -> dateUtil.minAgo(resourceHelper, bolusTime.time)
else -> dateUtil.hourAgo(bolusTime.time, resourceHelper)
}
binding.lastBolus.text = resourceHelper.gs(R.string.mdt_last_bolus, bolus, unit, ago)
} else {
binding.lastBolus.text = ""
}
val pumpState = pumpSync.expectedPumpState()
// base basal rate
binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") "
+ 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
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)
warnColors.setColorInverse(binding.reservoir, medtronicPumpStatus.reservoirRemainingUnits, 50.0, 20.0)
medtronicPumpPlugin.rileyLinkService?.verifyConfiguration()
medtronicPumpPlugin.rileyLinkService.verifyConfiguration()
binding.errors.text = medtronicPumpStatus.errorInfo
val showRileyLinkBatteryLevel: Boolean = rileyLinkServiceData.showBatteryLevel

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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));
}
}

View file

@ -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)
}
}

View file

@ -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;
}
}

View file

@ -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)
}
}

View file

@ -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);
}

View file

@ -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>
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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
}

View file

@ -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());
}
}

View file

@ -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")
}
}

View file

@ -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;
}
}

View file

@ -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 ");
}

View file

@ -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)));
}
}

View file

@ -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)))
}
}

View file

@ -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 //
}
}

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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()
}
}

View file

@ -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;
}
}

View file

@ -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)
}
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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)
// }
}

View file

@ -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);
}
}

View file

@ -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)
}
}
}

View file

@ -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();
}
}

View file

@ -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()
}
}

View file

@ -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;
}
}

View file

@ -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()
}
}

View file

@ -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);
}
}

View file

@ -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)
}
}

View file

@ -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();
}
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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
}

View file

@ -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);
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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;
}
}

View file

@ -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]
}
}

View file

@ -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;
}
}

View file

@ -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
}
}
}

View file

@ -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;
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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() + "]";
}
}

View file

@ -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 + "]"
}
}

View file

@ -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() + "]";
}
}

View file

@ -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 + "]"
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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()
}
}

View file

@ -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() + "]";
}
}

View file

@ -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 + "]"
}
}

View file

@ -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);
}
}

View file

@ -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)
}
}

View file

@ -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);
}
}

View file

@ -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)
}
}

View file

@ -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() + "]";
}
}

View file

@ -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 + "]")
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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, //
;
}

View file

@ -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
//
}

View file

@ -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;
}
}

View file

@ -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 //
);
}

View file

@ -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