Merge pull request #2613 from AAPS-Omnipod/dagger_omnipod_driver

omnipod eros driver and REQUIRED changes
This commit is contained in:
Milos Kozak 2020-05-03 13:21:16 +02:00 committed by GitHub
commit 1cee7e580f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
206 changed files with 14922 additions and 71 deletions

View file

@ -278,6 +278,7 @@ dependencies {
implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.2.2"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation(name: "com.atech-software.android.library.wizardpager-1.1.1", ext: "aar")
implementation("com.google.android:flexbox:0.3.0") {
exclude group: "com.android.support"

View file

@ -57,6 +57,7 @@ import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes;
import info.nightscout.androidaps.plugins.pump.insight.database.InsightBolusID;
import info.nightscout.androidaps.plugins.pump.insight.database.InsightHistoryOffset;
import info.nightscout.androidaps.plugins.pump.insight.database.InsightPumpID;
import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistory;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.utils.JsonHelper;
import info.nightscout.androidaps.utils.PercentageSplitter;
@ -86,8 +87,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public static final String DATABASE_INSIGHT_HISTORY_OFFSETS = "InsightHistoryOffsets";
public static final String DATABASE_INSIGHT_BOLUS_IDS = "InsightBolusIDs";
public static final String DATABASE_INSIGHT_PUMP_IDS = "InsightPumpIDs";
public static final String DATABASE_POD_HISTORY = "PodHistory";
private static final int DATABASE_VERSION = 11;
private static final int DATABASE_VERSION = 12;
public static Long earliestDataChange = null;
@ -135,6 +137,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class);
TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class);
TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class);
TableUtils.createTableIfNotExists(connectionSource, PodHistory.class);
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " +
"WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_BOLUS_IDS + "\")");
database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " +
@ -211,6 +214,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.dropTable(connectionSource, CareportalEvent.class, true);
TableUtils.dropTable(connectionSource, ProfileSwitch.class, true);
TableUtils.dropTable(connectionSource, TDD.class, true);
TableUtils.dropTable(connectionSource, PodHistory.class, true);
TableUtils.createTableIfNotExists(connectionSource, TempTarget.class);
TableUtils.createTableIfNotExists(connectionSource, BgReading.class);
TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class);
@ -220,6 +224,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class);
TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class);
TableUtils.createTableIfNotExists(connectionSource, TDD.class);
TableUtils.createTableIfNotExists(connectionSource, PodHistory.class);
updateEarliestDataChange(0);
} catch (SQLException e) {
log.error("Unhandled exception", e);
@ -354,6 +359,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
return getDao(InsightHistoryOffset.class);
}
private Dao<PodHistory, Long> getDaoPodHistory() throws SQLException {
return getDao(PodHistory.class);
}
public static long roundDateToSec(long date) {
long rounded = date - date % 1000;
if (rounded != date)
@ -1847,4 +1856,34 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
// ---------------- Food handling ---------------
// ---------------- PodHistory handling ---------------
public void createOrUpdate(PodHistory podHistory) {
try {
getDaoPodHistory().createOrUpdate(podHistory);
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
}
public List<PodHistory> getPodHistoryFromTime(long from, boolean ascending) {
try {
Dao<PodHistory, Long> daoPodHistory = getDaoPodHistory();
List<PodHistory> podHistories;
QueryBuilder<PodHistory, Long> queryBuilder = daoPodHistory.queryBuilder();
queryBuilder.orderBy("date", ascending);
//queryBuilder.limit(100L);
Where where = queryBuilder.where();
where.ge("date", from);
PreparedQuery<PodHistory> preparedQuery = queryBuilder.prepare();
podHistories = daoPodHistory.query(preparedQuery);
return podHistories;
} catch (SQLException e) {
log.error("Unhandled exception", e);
}
return new ArrayList<>();
}
}

View file

@ -67,6 +67,9 @@ public class Notification {
public static final int OVER_24H_TIME_CHANGE_REQUESTED = 54;
public static final int INVALID_VERSION = 55;
public static final int PERMISSION_SYSTEM_WINDOW = 56;
public static final int OMNIPOD_PUMP_ALARM = 57;
public static final int TIME_OR_TIMEZONE_CHANGE = 58;
public static final int OMNIPOD_POD_NOT_ATTACHED = 59;
public static final int USERMESSAGE = 1000;

View file

@ -57,11 +57,14 @@ import io.reactivex.schedulers.Schedulers;
public abstract class PumpPluginAbstract extends PumpPluginBase implements PumpInterface, ConstraintsInterface {
private CompositeDisposable disposable = new CompositeDisposable();
protected HasAndroidInjector injector;
protected AAPSLogger aapsLogger;
protected RxBusWrapper rxBus;
protected ActivePluginProvider activePlugin;
protected Context context;
protected FabricPrivacy fabricPrivacy;
protected ResourceHelper resourceHelper;
protected CommandQueueProvider commandQueue;
protected SP sp;
/*
@ -99,7 +102,9 @@ public abstract class PumpPluginAbstract extends PumpPluginBase implements PumpI
this.activePlugin = activePlugin;
this.context = context;
this.fabricPrivacy = fabricPrivacy;
this.resourceHelper = resourceHelper;
this.sp = sp;
this.commandQueue = commandQueue;
pumpDescription.setPumpDescription(pumpType);
this.pumpType = pumpType;
@ -109,12 +114,6 @@ public abstract class PumpPluginAbstract extends PumpPluginBase implements PumpI
public abstract void initPumpStatusData();
public abstract void resetRileyLinkConfiguration();
public abstract void doTuneUpDevice();
public abstract RileyLinkService getRileyLinkService();
@Override
protected void onStart() {
super.onStart();
@ -464,6 +463,7 @@ public abstract class PumpPluginAbstract extends PumpPluginBase implements PumpI
public void setPumpType(PumpType pumpType) {
this.pumpType = pumpType;
this.pumpDescription.setPumpDescription(pumpType);
}

View file

@ -49,16 +49,20 @@ public abstract class PumpStatus {
public int tempBasalRatio = 0;
public int tempBasalRemainMin = 0;
public Date tempBasalStart;
private PumpType pumpType;
//protected PumpDescription pumpDescription;
public PumpStatus() {
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 = DateUtil.now();

View file

@ -57,13 +57,13 @@ public enum PumpType {
new DoseSettings(0.01d, 15, 24 * 60, 0.05d), //
PumpTempBasalType.Percent,
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.02d, null, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities, false, false), //
0.02d, null, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), //
AccuChekSolo("Accu-Chek Solo", ManufacturerType.Roche, "Solo", 0.01d, null, //
new DoseSettings(0.01d, 15, 24 * 60, 0.05d), //
PumpTempBasalType.Percent,
new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, //
0.02d, null, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities, false, false), //
0.02d, null, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), //
// Animas

View file

@ -41,14 +41,20 @@ import javax.inject.Inject;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice;
import info.nightscout.androidaps.plugins.pump.common.utils.LocationHelper;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged;
import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
@ -60,8 +66,7 @@ public class RileyLinkBLEScanActivity extends NoSplashAppCompatActivity {
@Inject RxBusWrapper rxBus;
@Inject ResourceHelper resourceHelper;
@Inject RileyLinkUtil rileyLinkUtil;
// TODO change this. Currently verifyConfiguration uses MDT data not only RL
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
@Inject ActivePluginProvider activePlugin;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary.
private static final int REQUEST_ENABLE_BT = 30242; // arbitrary
@ -110,10 +115,25 @@ public class RileyLinkBLEScanActivity extends NoSplashAppCompatActivity {
sp.putString(RileyLinkConst.Prefs.RileyLinkAddress, bleAddress);
medtronicPumpPlugin.getRileyLinkService().verifyConfiguration(); // force reloading of address
PumpInterface activePump = activePlugin.getActivePump();
if (activePump.manufacturer()== ManufacturerType.Medtronic) {
RileyLinkPumpDevice rileyLinkPump = (RileyLinkPumpDevice)activePump;
rileyLinkPump.getRileyLinkService().verifyConfiguration(); // force reloading of address
rxBus.send(new EventMedtronicPumpConfigurationChanged());
} else if (activePlugin.getActivePump().manufacturer()== ManufacturerType.Insulet) {
if (activePump.model()== PumpType.Insulet_Omnipod_Dash) {
aapsLogger.error("Omnipod Dash not yet implemented.");
} else {
RileyLinkPumpDevice rileyLinkPump = (RileyLinkPumpDevice)activePump;
rileyLinkPump.getRileyLinkService().verifyConfiguration(); // force reloading of address
rxBus.send(new EventOmnipodPumpValuesChanged());
}
}
finish();
});

View file

@ -21,7 +21,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks
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.medtronic.defs.PumpDeviceState;
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
import info.nightscout.androidaps.utils.Round;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
/**
@ -33,16 +33,14 @@ public abstract class RileyLinkCommunicationManager {
@Inject protected AAPSLogger aapsLogger;
@Inject protected SP sp;
@Inject MedtronicPumpStatus medtronicPumpStatus;
@Inject RileyLinkServiceData rileyLinkServiceData;
@Inject ServiceTaskExecutor serviceTaskExecutor;
@Inject protected RileyLinkServiceData rileyLinkServiceData;
@Inject protected ServiceTaskExecutor serviceTaskExecutor;
private final int SCAN_TIMEOUT = 1500;
private final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes
protected final HasAndroidInjector injector;
public final HasAndroidInjector injector;
protected final RFSpy rfspy;
protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation
protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy))
@ -77,7 +75,7 @@ public abstract class RileyLinkCommunicationManager {
return sendAndListen(msg, timeout_ms, repeatCount, 0, extendPreamble_ms, clazz);
}
private <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, int retryCount, Integer extendPreamble_ms, Class<E> clazz)
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, int retryCount, Integer extendPreamble_ms, Class<E> clazz)
throws RileyLinkCommunicationException {
// internal flag
@ -129,6 +127,9 @@ public abstract class RileyLinkCommunicationManager {
public abstract <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz);
public abstract void setPumpDeviceState(PumpDeviceState pumpDeviceState);
public void wakeUp(boolean force) {
wakeUp(receiverDeviceAwakeForMinutes, force);
}
@ -150,7 +151,7 @@ public abstract class RileyLinkCommunicationManager {
// **** FIXME: this wakeup doesn't seem to work well... must revisit
// receiverDeviceAwakeForMinutes = duration_minutes;
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.WakingUp);
setPumpDeviceState(PumpDeviceState.WakingUp);
if (force)
nextWakeUpRequired = 0L;
@ -208,7 +209,7 @@ public abstract class RileyLinkCommunicationManager {
double[] scanFrequencies = rileyLinkServiceData.rileyLinkTargetFrequency.getScanFrequencies();
if (scanFrequencies.length == 1) {
return RileyLinkUtil.isSame(scanFrequencies[0], frequency);
return Round.isSame(scanFrequencies[0], frequency);
} else {
return (scanFrequencies[0] <= frequency && scanFrequencies[scanFrequencies.length - 1] >= frequency);
}

View file

@ -18,6 +18,7 @@ import javax.inject.Singleton;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6b;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6bGeoff;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.BleAdvertisedData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult;
@ -37,6 +38,9 @@ public class RileyLinkUtil {
private RileyLinkEncodingType encoding;
private Encoding4b6b encoding4b6b;
// TODO maybe not needed
private RileyLinkTargetFrequency rileyLinkTargetFrequency;
@Inject
public RileyLinkUtil() {
}
@ -154,4 +158,8 @@ public class RileyLinkUtil {
public Encoding4b6b getEncoding4b6b() {
return encoding4b6b;
}
public void setRileyLinkTargetFrequency(RileyLinkTargetFrequency rileyLinkTargetFrequency_) {
this.rileyLinkTargetFrequency = rileyLinkTargetFrequency_;
}
}

View file

@ -57,6 +57,7 @@ public class RFSpy {
private UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA);
private UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION);
private UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT);
private RileyLinkFirmwareVersion firmwareVersion;
private String bleVersion; // We don't use it so no need of sofisticated logic
private Double currentFrequencyMHz;
@ -68,6 +69,12 @@ public class RFSpy {
reader = new RFSpyReader(aapsLogger, rileyLinkBle);
}
public RileyLinkFirmwareVersion getRLVersionCached() {
return firmwareVersion;
}
public String getBLEVersionCached() {
return bleVersion;
}

View file

@ -8,6 +8,8 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLin
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType;
/**
* Created by andy on 5/19/18.
@ -23,6 +25,7 @@ public class RLHistoryItem {
private RileyLinkTargetDevice targetDevice;
private PumpDeviceState pumpDeviceState;
private OmnipodCommandType omnipodCommandType;
public RLHistoryItem(RileyLinkServiceState serviceState, RileyLinkError errorCode,
@ -50,6 +53,13 @@ public class RLHistoryItem {
}
public RLHistoryItem(OmnipodCommandType omnipodCommandType) {
this.dateTime = new LocalDateTime();
this.omnipodCommandType = omnipodCommandType;
source = RLHistoryItemSource.OmnipodCommand;
}
public LocalDateTime getDateTime() {
return dateTime;
}
@ -79,6 +89,9 @@ public class RLHistoryItem {
case MedtronicCommand:
return medtronicCommandType.name();
case OmnipodCommand:
return omnipodCommandType.name();
default:
return "Unknown Description";
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService;
public interface RileyLinkPumpDevice {
void setIsBusy(boolean isBusy_);
boolean isBusy();
void resetRileyLinkConfiguration();
boolean hasTuneUp();
void doTuneUpDevice();
RileyLinkService getRileyLinkService();
}

View file

@ -13,7 +13,15 @@ import java.util.Locale;
import javax.inject.Inject;
import dagger.android.support.DaggerFragment;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract;
import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion;
@ -23,6 +31,7 @@ import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
/**
@ -31,11 +40,10 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper;
public class RileyLinkStatusGeneralFragment extends DaggerFragment implements RefreshableInterface {
@Inject RileyLinkUtil rileyLinkUtil;
@Inject MedtronicUtil medtronicUtil;
@Inject MedtronicPumpStatus medtronicPumpStatus;
@Inject ActivePluginProvider activePlugin;
@Inject ResourceHelper resourceHelper;
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
@Inject MedtronicUtil medtronicUtil;
@Inject AAPSLogger aapsLogger;
@Inject RileyLinkServiceData rileyLinkServiceData;
TextView connectionStatus;
@ -120,16 +128,16 @@ public class RileyLinkStatusGeneralFragment extends DaggerFragment implements Re
}
// TODO add handling for Omnipod pump status
PumpPluginAbstract pumpPlugin = (PumpPluginAbstract)activePlugin.getActivePump();
if (pumpPlugin.manufacturer()== ManufacturerType.Medtronic) {
MedtronicPumpStatus medtronicPumpStatus = (MedtronicPumpStatus)pumpPlugin.getPumpStatusData();
if (medtronicPumpStatus != null) {
this.deviceType.setText(resourceHelper.gs(RileyLinkTargetDevice.MedtronicPump.getResourceId()));
this.deviceModel.setText(medtronicPumpPlugin.getPumpDescription().pumpType.getDescription());
this.deviceModel.setText(pumpPlugin.getPumpType().getDescription());
this.serialNumber.setText(medtronicPumpStatus.serialNumber);
this.pumpFrequency.setText(resourceHelper.gs(medtronicPumpStatus.pumpFrequency.equals("medtronic_pump_frequency_us_ca") ? R.string.medtronic_pump_frequency_us_ca : R.string.medtronic_pump_frequency_worldwide));
// TODO extend when Omnipod used
if (medtronicUtil.getMedtronicPumpModel() != null)
this.connectedDevice.setText("Medtronic " + medtronicUtil.getMedtronicPumpModel().getPumpModel());
else
@ -143,7 +151,44 @@ public class RileyLinkStatusGeneralFragment extends DaggerFragment implements Re
this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime(
medtronicPumpStatus.lastDataTime)));
else
this.lastDeviceContact.setText("Never");
this.lastDeviceContact.setText(resourceHelper.gs(R.string.common_never));
} else {
OmnipodPumpStatus omnipodPumpStatus = (OmnipodPumpStatus)pumpPlugin.getPumpStatusData();
this.deviceType.setText(resourceHelper.gs(RileyLinkTargetDevice.Omnipod.getResourceId()));
this.deviceModel.setText(pumpPlugin.getPumpType() == PumpType.Insulet_Omnipod ? "Eros" : "Dash");
if (pumpPlugin.getPumpType()== PumpType.Insulet_Omnipod_Dash) {
aapsLogger.error("Omnipod Dash not yet supported !!!");
this.pumpFrequency.setText("-");
} else {
this.pumpFrequency.setText(resourceHelper.gs(R.string.omnipod_frequency));
if (omnipodPumpStatus != null) {
if (omnipodPumpStatus.podAvailable) {
this.serialNumber.setText(omnipodPumpStatus.podLotNumber);
this.connectedDevice.setText(omnipodPumpStatus.pumpType == PumpType.Insulet_Omnipod ? "Eros Pod" : "Dash Pod");
} else {
this.serialNumber.setText("??");
this.connectedDevice.setText("-");
}
if (rileyLinkServiceData.lastGoodFrequency != null)
this.lastUsedFrequency.setText(String.format(Locale.ENGLISH, "%.2f MHz",
rileyLinkServiceData.lastGoodFrequency));
if (omnipodPumpStatus.lastConnection != 0)
this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime(
omnipodPumpStatus.lastDataTime)));
else
this.lastDeviceContact.setText(resourceHelper.gs(R.string.common_never));
}
}
}
}

View file

@ -10,6 +10,8 @@ import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import dagger.android.DaggerService;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
@ -27,6 +29,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
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 info.nightscout.androidaps.utils.sharedPreferences.SP;
/**
@ -40,9 +43,10 @@ public abstract class RileyLinkService extends DaggerService {
@Inject protected Context context;
@Inject protected RxBusWrapper rxBus;
@Inject protected RileyLinkUtil rileyLinkUtil;
@Inject protected MedtronicUtil medtronicUtil; // TODO should be avoided here as it's MDT
@Inject protected HasAndroidInjector injector;
@Inject protected ResourceHelper resourceHelper;
@Inject protected RileyLinkServiceData rileyLinkServiceData;
@Inject protected MedtronicPumpStatus medtronicPumpStatus;
@Inject protected ActivePluginProvider activePlugin;
@NotNull protected RileyLinkBLE rileyLinkBLE; // android-bluetooth management, must be set in initRileyLinkServiceData
protected BluetoothAdapter bluetoothAdapter;
@ -202,11 +206,13 @@ public abstract class RileyLinkService extends DaggerService {
}
// FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc.
public void doTuneUpDevice() {
rileyLinkServiceData.setRileyLinkServiceState(RileyLinkServiceState.TuneUpDevice);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
setPumpDeviceState(PumpDeviceState.Sleeping);
double lastGoodFrequency = 0.0d;
@ -238,6 +244,9 @@ public abstract class RileyLinkService extends DaggerService {
}
public abstract void setPumpDeviceState(PumpDeviceState pumpDeviceState);
public void disconnectRileyLink() {
if (rileyLinkBLE.isConnected()) {
@ -272,4 +281,6 @@ public abstract class RileyLinkService extends DaggerService {
else
return null;
}
public abstract boolean verifyConfiguration();
}

View file

@ -8,14 +8,18 @@ import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract;
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.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
import info.nightscout.androidaps.utils.Round;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
/**
@ -62,7 +66,10 @@ public class InitializePumpManagerTask extends ServiceTask {
lastGoodFrequency = rileyLinkServiceData.lastGoodFrequency;
}
RileyLinkCommunicationManager rileyLinkCommunicationManager = ((PumpPluginAbstract) activePlugin.getActivePump()).getRileyLinkService().getDeviceCommunicationManager();
// TODO Omnipod/Dagger needs refactoring
RileyLinkCommunicationManager rileyLinkCommunicationManager = ((RileyLinkPumpDevice) activePlugin.getActivePump()).getRileyLinkService().getDeviceCommunicationManager();
if (activePlugin.getActivePump().manufacturer() == ManufacturerType.Medtronic) {
if ((lastGoodFrequency > 0.0d)
&& rileyLinkCommunicationManager.isValidFrequency(lastGoodFrequency)) {
@ -86,5 +93,26 @@ public class InitializePumpManagerTask extends ServiceTask {
} else {
rileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump, context);
}
} else {
if (!Round.isSame(lastGoodFrequency, RileyLinkTargetFrequency.Omnipod.getScanFrequencies()[0])) {
lastGoodFrequency = RileyLinkTargetFrequency.Omnipod.getScanFrequencies()[0];
lastGoodFrequency = Math.round(lastGoodFrequency * 1000d) / 1000d;
rileyLinkServiceData.lastGoodFrequency = lastGoodFrequency;
}
rileyLinkServiceData.setRileyLinkServiceState(RileyLinkServiceState.RileyLinkReady);
rileyLinkUtil.setRileyLinkTargetFrequency(RileyLinkTargetFrequency.Omnipod);
aapsLogger.info(LTag.PUMPCOMM, "Setting radio frequency to {} MHz", lastGoodFrequency);
rileyLinkCommunicationManager.setRadioFrequencyForPump(lastGoodFrequency);
rileyLinkServiceData.setRileyLinkServiceState(RileyLinkServiceState.PumpConnectorReady);
}
}
}

View file

@ -4,10 +4,11 @@ import javax.inject.Inject;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.common.ManufacturerType;
import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState;
@ -21,9 +22,6 @@ public class ResetRileyLinkConfigurationTask extends PumpTask {
@Inject ActivePluginProvider activePlugin;
@Inject RxBusWrapper rxBus;
private static final String TAG = "ResetRileyLinkTask";
public ResetRileyLinkConfigurationTask(HasAndroidInjector injector) {
super(injector);
}
@ -36,12 +34,16 @@ public class ResetRileyLinkConfigurationTask extends PumpTask {
@Override
public void run() {
PumpPluginAbstract pump = (PumpPluginAbstract) activePlugin.getActivePump();
RileyLinkPumpDevice pumpAbstract = (RileyLinkPumpDevice)activePlugin.getActivePump();
rxBus.send(new EventRefreshButtonState(false));
MedtronicPumpPlugin.isBusy = true;
pump.resetRileyLinkConfiguration();
MedtronicPumpPlugin.isBusy = false;
pumpAbstract.setIsBusy(true);
pumpAbstract.resetRileyLinkConfiguration();
pumpAbstract.setIsBusy(false);
rxBus.send(new EventRefreshButtonState(true));
}
}

View file

@ -6,6 +6,7 @@ import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState;
@ -33,11 +34,11 @@ public class WakeAndTuneTask extends PumpTask {
@Override
public void run() {
PumpPluginAbstract pump = (PumpPluginAbstract) activePlugin.getActivePump();
RileyLinkPumpDevice pumpDevice = (RileyLinkPumpDevice)activePlugin.getActivePump();
rxBus.send(new EventRefreshButtonState(false));
MedtronicPumpPlugin.isBusy = true;
pump.doTuneUpDevice();
MedtronicPumpPlugin.isBusy = false;
pumpDevice.setIsBusy(true);
pumpDevice.doTuneUpDevice();
pumpDevice.setIsBusy(false);
rxBus.send(new EventRefreshButtonState(true));
}
}

View file

@ -57,6 +57,7 @@ import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkPumpDevice;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ResetRileyLinkConfigurationTask;
@ -95,7 +96,7 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP;
* @author Andy Rozman (andy.rozman@gmail.com)
*/
@Singleton
public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInterface {
public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInterface, RileyLinkPumpDevice {
private final SP sp;
private final RileyLinkUtil rileyLinkUtil;
@ -234,6 +235,11 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter
rileyLinkMedtronicService.resetRileyLinkConfiguration();
}
@Override
public boolean hasTuneUp() {
return true;
}
@Override public void doTuneUpDevice() {
rileyLinkMedtronicService.doTuneUpDevice();
}
@ -329,6 +335,11 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter
}
@Override
public void setIsBusy(boolean isBusy_) {
isBusy = isBusy_;
}
@Override
public boolean isBusy() {
if (displayConnectionMessages)

View file

@ -92,6 +92,11 @@ public class MedtronicCommunicationManager extends RileyLinkCommunicationManager
return (E) pumpMessage;
}
@Override
public void setPumpDeviceState(PumpDeviceState pumpDeviceState) {
this.medtronicPumpStatus.setPumpDeviceState(pumpDeviceState);
}
public void setDoWakeUpBeforeCommand(boolean doWakeUp) {
this.doWakeUpBeforeCommand = doWakeUp;
}

View file

@ -56,6 +56,7 @@ import info.nightscout.androidaps.plugins.treatments.Treatment;
import info.nightscout.androidaps.plugins.treatments.TreatmentService;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.Round;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
@ -659,7 +660,7 @@ public class MedtronicHistoryData {
Treatment treatment = (Treatment) dbObjectBase;
if (RileyLinkUtil.isSame(treatment.insulin, 0d)) {
if (Round.isSame(treatment.insulin, 0d)) {
removeList.add(dbObjectBase);
}
}

View file

@ -60,13 +60,12 @@ public class MedtronicPumpStatus extends PumpStatus {
@Inject
public MedtronicPumpStatus(
ResourceHelper resourceHelper,
public MedtronicPumpStatus(ResourceHelper resourceHelper,
SP sp,
RxBusWrapper rxBus,
RileyLinkUtil rileyLinkUtil
) {
super();
super(PumpType.Medtronic_522_722);
this.resourceHelper = resourceHelper;
this.sp = sp;
this.rxBus = rxBus;
@ -75,7 +74,7 @@ public class MedtronicPumpStatus extends PumpStatus {
}
private void initSettings() {
public void initSettings() {
this.activeProfileName = "STD";
this.reservoirRemainingUnits = 75d;

View file

@ -38,8 +38,6 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper;
*/
public class RileyLinkMedtronicService extends RileyLinkService {
@Inject HasAndroidInjector injector;
@Inject ResourceHelper resourceHelper;
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
@Inject MedtronicUtil medtronicUtil;
@Inject MedtronicUIPostprocessor medtronicUIPostprocessor;
@ -120,6 +118,12 @@ public class RileyLinkMedtronicService extends RileyLinkService {
}
@Override
public void setPumpDeviceState(PumpDeviceState pumpDeviceState) {
this.medtronicPumpStatus.setPumpDeviceState(pumpDeviceState);
}
public MedtronicUIComm getMedtronicUIComm() {
return medtronicUIComm;
}
@ -236,7 +240,7 @@ public class RileyLinkMedtronicService extends RileyLinkService {
} else {
PumpType pumpType = medtronicPumpStatus.getMedtronicPumpMap().get(pumpTypePart);
medtronicPumpStatus.medtronicDeviceType = medtronicPumpStatus.getMedtronicDeviceTypeMap().get(pumpTypePart);
medtronicPumpPlugin.getPumpDescription().setPumpDescription(pumpType);
medtronicPumpPlugin.setPumpType(pumpType);
if (pumpTypePart.startsWith("7"))
medtronicPumpStatus.reservoirFullUnits = 300;

View file

@ -0,0 +1,458 @@
package info.nightscout.androidaps.plugins.pump.omnipod
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.CommandQueueProvider
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.PumpType
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity
import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodStatusRequest
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState
import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.PodManagementActivity
import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState
import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus
import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodAcknowledgeAlertsChanged
import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange
import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged
import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodRefreshButtonState
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.Round
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.WarnColors
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.omnipod_fragment.*
import javax.inject.Inject
class OmnipodFragment : DaggerFragment() {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var mainApp: MainApp
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var commandQueue: CommandQueueProvider
@Inject lateinit var activePlugin: ActivePluginProvider
@Inject lateinit var omnipodPumpPlugin: OmnipodPumpPlugin
@Inject lateinit var warnColors: WarnColors
@Inject lateinit var omnipodPumpStatus: OmnipodPumpStatus
@Inject lateinit var sp: SP
@Inject lateinit var omnipodUtil: OmnipodUtil
private var disposable: CompositeDisposable = CompositeDisposable()
private val loopHandler = Handler()
private lateinit var refreshLoop: Runnable
operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
add(disposable)
}
init {
refreshLoop = Runnable {
activity?.runOnUiThread { updateGUI() }
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.omnipod_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
omnipod_rl_status.text = resourceHelper.gs(RileyLinkServiceState.NotStarted.getResourceId(RileyLinkTargetDevice.Omnipod))
omnipod_pod_status.setTextColor(Color.WHITE)
omnipod_pod_status.text = "{fa-bed}"
omnipod_pod_mgmt.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
startActivity(Intent(context, PodManagementActivity::class.java))
} else {
displayNotConfiguredDialog()
}
}
omnipod_refresh.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() != true) {
OmnipodUtil.displayNotConfiguredDialog(context)
} else {
omnipod_refresh.isEnabled = false
omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequest.GetPodState);
commandQueue.readStatus("Clicked Refresh", object : Callback() {
override fun run() {
activity?.runOnUiThread { omnipod_refresh.isEnabled = true }
}
})
}
}
omnipod_stats.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() == true) {
startActivity(Intent(context, RileyLinkStatusActivity::class.java))
} else {
displayNotConfiguredDialog()
}
}
omnipod_pod_active_alerts_ack.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() != true) {
displayNotConfiguredDialog()
} else {
omnipod_pod_active_alerts_ack.isEnabled = false
omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequest.AcknowledgeAlerts);
commandQueue.readStatus("Clicked Alert Ack", null)
}
}
omnipod_pod_debug.setOnClickListener {
if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() != true) {
displayNotConfiguredDialog()
} else {
omnipod_pod_debug.isEnabled = false
omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequest.GetPodPulseLog);
commandQueue.readStatus("Clicked Refresh", object : Callback() {
override fun run() {
activity?.runOnUiThread { omnipod_pod_debug.isEnabled = true }
}
})
}
}
omnipod_lastconnection.setTextColor(Color.WHITE)
setVisibilityOfPodDebugButton()
updateGUI()
}
override fun onResume() {
super.onResume()
loopHandler.postDelayed(refreshLoop, T.mins(1).msecs())
disposable += rxBus
.toObservable(EventOmnipodRefreshButtonState::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ omnipod_refresh.isEnabled = it.newState }, { fabricPrivacy.logException(it) })
disposable += rxBus
.toObservable(EventOmnipodDeviceStatusChange::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
aapsLogger.info(LTag.PUMP, "onStatusEvent(EventOmnipodDeviceStatusChange): {}", it)
setDeviceStatus()
}, { fabricPrivacy.logException(it) })
disposable += rxBus
.toObservable(EventOmnipodPumpValuesChanged::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ updateGUI() }, { fabricPrivacy.logException(it) })
disposable += rxBus
.toObservable(EventOmnipodAcknowledgeAlertsChanged::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ updateAcknowledgeAlerts() }, { fabricPrivacy.logException(it) })
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ event ->
setVisibilityOfPodDebugButton()
}, { fabricPrivacy.logException(it) })
}
fun setVisibilityOfPodDebugButton() {
val isEnabled = sp.getBoolean(OmnipodConst.Prefs.PodDebuggingOptionsEnabled, false)
if (isEnabled)
omnipod_pod_debug.visibility = View.VISIBLE
else
omnipod_pod_debug.visibility = View.GONE
}
private fun displayNotConfiguredDialog() {
context?.let {
OKDialog.show(it, resourceHelper.gs(R.string.combo_warning),
resourceHelper.gs(R.string.omnipod_error_operation_not_possible_no_configuration), null)
}
}
override fun onPause() {
super.onPause()
disposable.clear()
loopHandler.removeCallbacks(refreshLoop)
}
@Synchronized
private fun setDeviceStatus() {
//val omnipodPumpStatus: OmnipodPumpStatus = OmnipodUtil.getPumpStatus()
// omnipodPumpStatus.rileyLinkServiceState = checkStatusSet(omnipodPumpStatus.rileyLinkServiceState,
// RileyLinkUtil.getServiceState()) as RileyLinkServiceState?
aapsLogger.info(LTag.PUMP, "setDeviceStatus: [pumpStatus={}]", omnipodPumpStatus)
val resourceId = omnipodPumpStatus.rileyLinkServiceState.getResourceId(RileyLinkTargetDevice.Omnipod)
val rileyLinkError = omnipodPumpPlugin.rileyLinkService?.error
omnipod_rl_status.text =
when {
omnipodPumpStatus.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> resourceHelper.gs(resourceId)
omnipodPumpStatus.rileyLinkServiceState.isConnecting -> "{fa-bluetooth-b spin} " + resourceHelper.gs(resourceId)
omnipodPumpStatus.rileyLinkServiceState.isError && rileyLinkError == null -> "{fa-bluetooth-b} " + resourceHelper.gs(resourceId)
omnipodPumpStatus.rileyLinkServiceState.isError && rileyLinkError != null -> "{fa-bluetooth-b} " + resourceHelper.gs(rileyLinkError.getResourceId(RileyLinkTargetDevice.MedtronicPump))
else -> "{fa-bluetooth-b} " + resourceHelper.gs(resourceId)
}
omnipod_rl_status.setTextColor(if (rileyLinkError != null) Color.RED else Color.WHITE)
// omnipodPumpStatus.rileyLinkError = checkStatusSet(omnipodPumpStatus.rileyLinkError,
// RileyLinkUtil.getError()) as RileyLinkError?
omnipod_errors.text =
omnipodPumpStatus.rileyLinkError?.let {
resourceHelper.gs(it.getResourceId(RileyLinkTargetDevice.Omnipod))
} ?: "-"
val driverState = omnipodUtil.getDriverState();
aapsLogger.info(LTag.PUMP, "getDriverState: [driverState={}]", driverState)
if (driverState == OmnipodDriverState.NotInitalized) {
omnipod_pod_address.text = resourceHelper.gs(R.string.omnipod_pod_name_no_info)
omnipod_pod_expiry.text = "-"
omnipod_pod_status.text = resourceHelper.gs(R.string.omnipod_pod_not_initalized)
omnipodPumpStatus.podAvailable = false
omnipodPumpStatus.podNumber == null
} else if (driverState == OmnipodDriverState.Initalized_NoPod) {
omnipod_pod_address.text = resourceHelper.gs(R.string.omnipod_pod_name_no_info)
omnipod_pod_expiry.text = "-"
omnipod_pod_status.text = resourceHelper.gs(R.string.omnipod_pod_no_pod_connected)
omnipodPumpStatus.podAvailable = false
omnipodPumpStatus.podNumber == null
} else if (driverState == OmnipodDriverState.Initalized_PodInitializing) {
omnipod_pod_address.text = omnipodPumpStatus.podSessionState.address.toString()
omnipod_pod_expiry.text = "-"
omnipod_pod_status.text = omnipodPumpStatus.podSessionState.getSetupProgress().name
omnipodPumpStatus.podAvailable = false
omnipodPumpStatus.podNumber == omnipodPumpStatus.podSessionState.address.toString()
} else {
omnipodPumpStatus.podLotNumber = "" + omnipodPumpStatus.podSessionState.lot
omnipodPumpStatus.podAvailable = true
omnipod_pod_address.text = omnipodPumpStatus.podSessionState.address.toString()
omnipod_pod_expiry.text = omnipodPumpStatus.podSessionState.expiryDateAsString
omnipodPumpStatus.podNumber = omnipodPumpStatus.podSessionState.address.toString()
//pumpStatus.podSessionState = checkStatusSet(pumpStatus.podSessionState,
// OmnipodUtil.getPodSessionState()) as PodSessionState?
var podDeviceState = omnipodPumpStatus.podDeviceState
when (podDeviceState) {
null,
PodDeviceState.Sleeping -> omnipod_pod_status.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
PodDeviceState.NeverContacted,
PodDeviceState.WakingUp,
PodDeviceState.PumpUnreachable,
PodDeviceState.ErrorWhenCommunicating,
PodDeviceState.TimeoutWhenCommunicating,
PodDeviceState.InvalidConfiguration -> omnipod_pod_status.text = " " + resourceHelper.gs(podDeviceState.resourceId)
PodDeviceState.Active -> {
omnipod_pod_status.text = "Active";
// val cmd = OmnipodUtil.getCurrentCommand()
// if (cmd == null)
// omnipod_pod_status.text = " " + resourceHelper.gs(pumpStatus.pumpDeviceState.resourceId)
// else {
// aapsLogger.debug(LTag.PUMP,"Command: " + cmd)
// val cmdResourceId = cmd.resourceId
// if (cmd == MedtronicCommandType.GetHistoryData) {
// omnipod_pod_status.text = OmnipodUtil.frameNumber?.let {
// resourceHelper.gs(cmdResourceId, OmnipodUtil.pageNumber, OmnipodUtil.frameNumber)
// }
// ?: resourceHelper.gs(R.string.medtronic_cmd_desc_get_history_request, OmnipodUtil.pageNumber)
// } else {
// omnipod_pod_status.text = " " + (cmdResourceId?.let { resourceHelper.gs(it) }
// ?: cmd.getCommandDescription())
// }
// }
}
else -> aapsLogger.warn(LTag.PUMP, "Unknown pump state: " + omnipodPumpStatus.podDeviceState)
}
}
// pumpStatus.pumpDeviceState = checkStatusSet(pumpStatus.pumpDeviceState,
// OmnipodUtil.getPumpDeviceState()) as PumpDeviceState?
//
// when (pumpStatus.pumpDeviceState) {
// null,
// PumpDeviceState.Sleeping -> omnipod_pod_status.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
// PumpDeviceState.NeverContacted,
// PumpDeviceState.WakingUp,
// PumpDeviceState.PumpUnreachable,
// PumpDeviceState.ErrorWhenCommunicating,
// PumpDeviceState.TimeoutWhenCommunicating,
// PumpDeviceState.InvalidConfiguration -> omnipod_pod_status.text = " " + resourceHelper.gs(pumpStatus.pumpDeviceState.resourceId)
// PumpDeviceState.Active -> {
// val cmd = OmnipodUtil.getCurrentCommand()
// if (cmd == null)
// omnipod_pod_status.text = " " + resourceHelper.gs(pumpStatus.pumpDeviceState.resourceId)
// else {
// aapsLogger.debug(LTag.PUMP,"Command: " + cmd)
// val cmdResourceId = cmd.resourceId
// if (cmd == MedtronicCommandType.GetHistoryData) {
// omnipod_pod_status.text = OmnipodUtil.frameNumber?.let {
// resourceHelper.gs(cmdResourceId, OmnipodUtil.pageNumber, OmnipodUtil.frameNumber)
// }
// ?: resourceHelper.gs(R.string.medtronic_cmd_desc_get_history_request, OmnipodUtil.pageNumber)
// } else {
// omnipod_pod_status.text = " " + (cmdResourceId?.let { resourceHelper.gs(it) }
// ?: cmd.getCommandDescription())
// }
// }
// }
// else -> aapsLogger.warn(LTag.PUMP,"Unknown pump state: " + pumpStatus.pumpDeviceState)
// }
val status = commandQueue.spannedStatus()
if (status.toString() == "") {
omnipod_queue.visibility = View.GONE
} else {
omnipod_queue.visibility = View.VISIBLE
omnipod_queue.text = status
}
}
private fun checkStatusSet(object1: Any?, object2: Any?): Any? {
return if (object1 == null) {
object2
} else {
if (object1 != object2) {
object2
} else
object1
}
}
// GUI functions
fun updateGUI() {
val plugin = omnipodPumpPlugin
//val omnipodPumpStatus = OmnipodUtil.getPumpStatus()
var pumpType = omnipodPumpStatus.pumpType
if (pumpType == null) {
aapsLogger.warn(LTag.PUMP, "PumpType was not set, reseting to Omnipod.")
pumpType = PumpType.Insulet_Omnipod;
}
setDeviceStatus()
if (omnipodPumpStatus.podAvailable) {
// last connection
if (omnipodPumpStatus.lastConnection != 0L) {
//val minAgo = DateUtil.minAgo(pumpStatus.lastConnection)
val min = (System.currentTimeMillis() - omnipodPumpStatus.lastConnection) / 1000 / 60
if (omnipodPumpStatus.lastConnection + 60 * 1000 > System.currentTimeMillis()) {
omnipod_lastconnection.setText(R.string.combo_pump_connected_now)
//omnipod_lastconnection.setTextColor(Color.WHITE)
} else { //if (pumpStatus.lastConnection + 30 * 60 * 1000 < System.currentTimeMillis()) {
if (min < 60) {
omnipod_lastconnection.text = resourceHelper.gs(R.string.minago, min)
} else if (min < 1440) {
val h = (min / 60).toInt()
omnipod_lastconnection.text = (resourceHelper.gq(R.plurals.objective_hours, h, h) + " "
+ resourceHelper.gs(R.string.ago))
} else {
val h = (min / 60).toInt()
val d = h / 24
// h = h - (d * 24);
omnipod_lastconnection.text = (resourceHelper.gq(R.plurals.objective_days, d, d) + " "
+ resourceHelper.gs(R.string.ago))
}
//omnipod_lastconnection.setTextColor(Color.RED)
}
// } else {
// omnipod_lastconnection.text = minAgo
// //omnipod_lastconnection.setTextColor(Color.WHITE)
// }
}
// last bolus
val bolus = omnipodPumpStatus.lastBolusAmount
val bolusTime = omnipodPumpStatus.lastBolusTime
if (bolus != null && bolusTime != null && omnipodPumpStatus.podAvailable) {
val agoMsc = System.currentTimeMillis() - omnipodPumpStatus.lastBolusTime.time
val bolusMinAgo = agoMsc.toDouble() / 60.0 / 1000.0
val unit = resourceHelper.gs(R.string.insulin_unit_shortname)
val ago: String
if (agoMsc < 60 * 1000) {
ago = resourceHelper.gs(R.string.combo_pump_connected_now)
} else if (bolusMinAgo < 60) {
ago = DateUtil.minAgo(resourceHelper, omnipodPumpStatus.lastBolusTime.time)
} else {
ago = DateUtil.hourAgo(omnipodPumpStatus.lastBolusTime.time, resourceHelper)
}
omnipod_lastbolus.text = resourceHelper.gs(R.string.omnipod_last_bolus, pumpType.determineCorrectBolusSize(bolus), unit, ago)
} else {
omnipod_lastbolus.text = ""
}
// base basal rate
omnipod_basabasalrate.text = resourceHelper.gs(R.string.pump_basebasalrate, pumpType.determineCorrectBasalSize(plugin.baseBasalRate))
omnipod_tempbasal.text = activePlugin.activeTreatments
.getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: ""
// reservoir
if (Round.isSame(omnipodPumpStatus.reservoirRemainingUnits, 75.0)) {
omnipod_reservoir.text = resourceHelper.gs(R.string.omnipod_reservoir_over50)
} else {
omnipod_reservoir.text = resourceHelper.gs(R.string.omnipod_reservoir_left, omnipodPumpStatus.reservoirRemainingUnits)
}
warnColors.setColorInverse(omnipod_reservoir, omnipodPumpStatus.reservoirRemainingUnits, 50.0, 20.0)
} else {
omnipod_basabasalrate.text = ""
omnipod_reservoir.text = ""
omnipod_tempbasal.text = ""
omnipod_lastbolus.text = ""
omnipod_lastconnection.text = ""
omnipod_lastconnection.setTextColor(Color.WHITE)
}
omnipod_errors.text = omnipodPumpStatus.errorInfo
updateAcknowledgeAlerts()
omnipod_refresh.isEnabled = omnipodPumpStatus.podAvailable
}
private fun updateAcknowledgeAlerts() {
omnipod_pod_active_alerts_ack.isEnabled = omnipodPumpStatus.ackAlertsAvailable
omnipod_pod_active_alerts.text = omnipodPumpStatus.ackAlertsText
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm;
// TODO replace with Consumer when our min API level >= 24
@FunctionalInterface
public interface BolusProgressIndicationConsumer {
void accept(double estimatedUnitsDelivered, int percentage);
}

View file

@ -0,0 +1,345 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm;
import java.util.Collections;
import java.util.List;
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.pump.common.data.PumpStatus;
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.RFSpy;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState;
import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.OmnipodAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CommunicationException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalPacketTypeException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalResponseException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.NonceOutOfSyncException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.NonceResyncException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.NotEnoughDataException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.PodFaultException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.PodReturnedErrorResponseException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodPacket;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.DeactivatePodCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodState;
import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
/**
* Created by andy on 6/29/18.
*/
public class OmnipodCommunicationManager extends RileyLinkCommunicationManager {
@Inject public AAPSLogger aapsLogger;
@Inject OmnipodPumpStatus omnipodPumpStatus;
@Inject OmnipodPumpPlugin omnipodPumpPlugin;
@Inject RileyLinkServiceData rileyLinkServiceData;
@Inject ServiceTaskExecutor serviceTaskExecutor;
public OmnipodCommunicationManager(HasAndroidInjector injector, RFSpy rfspy) {
super(injector, rfspy);
omnipodPumpStatus.previousConnection = sp.getLong(
RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L);
}
// @Override
// protected void configurePumpSpecificSettings() {
// }
@Override
public boolean tryToConnectToDevice() {
// TODO
return false;
}
@Override
public byte[] createPumpMessageContent(RLMessageType type) {
return new byte[0];
}
@Override public PumpStatus getPumpStatus() {
return null;
}
@Override public boolean isDeviceReachable() {
return false;
}
@Override
public boolean hasTunning() {
return false;
}
@Override
public <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz) {
return (E) new OmnipodPacket(payload);
}
@Override
public void setPumpDeviceState(PumpDeviceState pumpDeviceState) {
this.omnipodPumpStatus.setPumpDeviceState(pumpDeviceState);
}
public <T extends MessageBlock> T sendCommand(Class<T> responseClass, PodState podState, MessageBlock command) {
return sendCommand(responseClass, podState, command, true);
}
public <T extends MessageBlock> T sendCommand(Class<T> responseClass, PodState podState, MessageBlock command, boolean automaticallyResyncNone) {
OmnipodMessage message = new OmnipodMessage(podState.getAddress(), Collections.singletonList(command), podState.getMessageNumber());
return exchangeMessages(responseClass, podState, message, automaticallyResyncNone);
}
// Convenience method
public <T> T executeAction(OmnipodAction<T> action) {
return action.execute(this);
}
public <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message) {
return exchangeMessages(responseClass, podState, message, true);
}
public <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message, boolean automaticallyResyncNonce) {
return exchangeMessages(responseClass, podState, message, null, null, automaticallyResyncNonce);
}
public synchronized <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
return exchangeMessages(responseClass, podState, message, addressOverride, ackAddressOverride, true);
}
public synchronized <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride, boolean automaticallyResyncNonce) {
aapsLogger.debug(LTag.PUMPCOMM, "Exchanging OmnipodMessage [responseClass={}, podState={}, message={}, addressOverride={}, ackAddressOverride={}, automaticallyResyncNonce={}]: {}", //
responseClass.getSimpleName(), podState, message, addressOverride, ackAddressOverride, automaticallyResyncNonce, message);
for (int i = 0; 2 > i; i++) {
if (podState.hasNonceState() && message.isNonceResyncable()) {
podState.advanceToNextNonce();
}
MessageBlock responseMessageBlock = transportMessages(podState, message, addressOverride, ackAddressOverride);
if (responseMessageBlock instanceof StatusResponse) {
podState.updateFromStatusResponse((StatusResponse) responseMessageBlock);
}
if (responseClass.isInstance(responseMessageBlock)) {
return (T) responseMessageBlock;
} else {
if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) {
ErrorResponse error = (ErrorResponse) responseMessageBlock;
if (error.getErrorResponseType() == ErrorResponseType.BAD_NONCE) {
podState.resyncNonce(error.getNonceSearchKey(), message.getSentNonce(), message.getSequenceNumber());
if (automaticallyResyncNonce) {
message.resyncNonce(podState.getCurrentNonce());
} else {
throw new NonceOutOfSyncException();
}
} else {
throw new PodReturnedErrorResponseException((ErrorResponse) responseMessageBlock);
}
} else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) {
PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo();
podState.setFaultEvent(faultEvent);
throw new PodFaultException(faultEvent);
} else {
throw new IllegalResponseException(responseClass.getSimpleName(), responseMessageBlock.getType());
}
}
}
throw new NonceResyncException();
}
private MessageBlock transportMessages(PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
int packetAddress = podState.getAddress();
if (addressOverride != null) {
packetAddress = addressOverride;
}
boolean firstPacket = true;
byte[] encodedMessage;
// this does not work well with the deactivate pod command, we somehow either
// receive an ACK instead of a normal response, or a partial response and a communication timeout
if (message.isNonceResyncable() && !message.containsBlock(DeactivatePodCommand.class)) {
OmnipodMessage paddedMessage = new OmnipodMessage(message);
// If messages are nonce resyncable, we want do distinguish between certain and uncertain failures for verification purposes
// However, some commands (e.g. cancel delivery) are single packet command by nature. When we get a timeout with a single packet,
// we are unsure whether or not the command was received by the pod
// However, if we send > 1 packet, we know that the command wasn't received if we never send the subsequent packets,
// because the last packet contains the CRC.
// So we pad the message with get status commands to make it > packet
paddedMessage.padWithGetStatusCommands(PacketType.PDM.getMaxBodyLength()); // First packet is of type PDM
encodedMessage = paddedMessage.getEncoded();
} else {
encodedMessage = message.getEncoded();
}
OmnipodPacket response = null;
while (encodedMessage.length > 0) {
PacketType packetType = firstPacket ? PacketType.PDM : PacketType.CON;
OmnipodPacket packet = new OmnipodPacket(packetAddress, packetType, podState.getPacketNumber(), encodedMessage);
byte[] encodedMessageInPacket = packet.getEncodedMessage();
// getting the data remaining to be sent
encodedMessage = ByteUtil.substring(encodedMessage, encodedMessageInPacket.length, encodedMessage.length - encodedMessageInPacket.length);
firstPacket = false;
try {
// We actually ignore previous (ack) responses if it was not last packet to send
response = exchangePackets(podState, packet);
} catch (Exception ex) {
OmnipodException newException;
if (ex instanceof OmnipodException) {
newException = (OmnipodException) ex;
} else {
newException = new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex);
}
boolean lastPacket = encodedMessage.length == 0;
// If this is not the last packet, the message wasn't fully sent,
// so it's impossible for the pod to have received the message
newException.setCertainFailure(!lastPacket);
aapsLogger.debug(LTag.PUMPCOMM, "Caught exception in transportMessages. Set certainFailure to {} because encodedMessage.length={}", newException.isCertainFailure(), encodedMessage.length);
throw newException;
}
}
if (response.getPacketType() == PacketType.ACK) {
podState.increasePacketNumber(1);
throw new IllegalPacketTypeException(null, PacketType.ACK);
}
OmnipodMessage receivedMessage = null;
byte[] receivedMessageData = response.getEncodedMessage();
while (receivedMessage == null) {
try {
receivedMessage = OmnipodMessage.decodeMessage(receivedMessageData);
} catch (NotEnoughDataException ex) {
// Message is (probably) not complete yet
OmnipodPacket ackForCon = createAckPacket(podState, packetAddress, ackAddressOverride);
try {
OmnipodPacket conPacket = exchangePackets(podState, ackForCon, 3, 40);
if (conPacket.getPacketType() != PacketType.CON) {
throw new IllegalPacketTypeException(PacketType.CON, conPacket.getPacketType());
}
receivedMessageData = ByteUtil.concat(receivedMessageData, conPacket.getEncodedMessage());
} catch (OmnipodException ex2) {
throw ex2;
} catch (Exception ex2) {
throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex2);
}
}
}
podState.increaseMessageNumber(2);
ackUntilQuiet(podState, packetAddress, ackAddressOverride);
List<MessageBlock> messageBlocks = receivedMessage.getMessageBlocks();
if (messageBlocks.size() == 0) {
throw new NotEnoughDataException(receivedMessageData);
} else if (messageBlocks.size() > 1) {
// BS: don't expect this to happen
aapsLogger.error(LTag.PUMPBTCOMM, "Received more than one message block: {}", messageBlocks.toString());
}
return messageBlocks.get(0);
}
private OmnipodPacket createAckPacket(PodState podState, Integer packetAddress, Integer messageAddress) {
int pktAddress = podState.getAddress();
int msgAddress = podState.getAddress();
if (packetAddress != null) {
pktAddress = packetAddress;
}
if (messageAddress != null) {
msgAddress = messageAddress;
}
return new OmnipodPacket(pktAddress, PacketType.ACK, podState.getPacketNumber(), ByteUtil.getBytesFromInt(msgAddress));
}
private void ackUntilQuiet(PodState podState, Integer packetAddress, Integer messageAddress) {
OmnipodPacket ack = createAckPacket(podState, packetAddress, messageAddress);
boolean quiet = false;
while (!quiet) try {
sendAndListen(ack, 300, 1, 0, 40, OmnipodPacket.class);
} catch (RileyLinkCommunicationException ex) {
if (RileyLinkBLEError.Timeout.equals(ex.getErrorCode())) {
quiet = true;
} else {
aapsLogger.debug(LTag.PUMPBTCOMM, "Ignoring exception in ackUntilQuiet", ex);
}
} catch (OmnipodException ex) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Ignoring exception in ackUntilQuiet", ex);
} catch (Exception ex) {
throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex);
}
podState.increasePacketNumber(1);
}
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet) {
return exchangePackets(podState, packet, 0, 333, 9000, 127);
}
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int preambleExtensionMilliseconds) {
return exchangePackets(podState, packet, repeatCount, 333, 9000, preambleExtensionMilliseconds);
}
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int responseTimeoutMilliseconds, int exchangeTimeoutMilliseconds, int preambleExtensionMilliseconds) {
long timeoutTime = System.currentTimeMillis() + exchangeTimeoutMilliseconds;
while (System.currentTimeMillis() < timeoutTime) {
OmnipodPacket response = null;
try {
response = sendAndListen(packet, responseTimeoutMilliseconds, repeatCount, 9, preambleExtensionMilliseconds, OmnipodPacket.class);
} catch (RileyLinkCommunicationException | OmnipodException ex) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Ignoring exception in exchangePackets", ex);
} catch (Exception ex) {
throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex);
}
if (response == null || !response.isValid()) {
continue;
}
if (response.getAddress() != packet.getAddress()) {
continue;
}
if (response.getSequenceNumber() != ((podState.getPacketNumber() + 1) & 0b11111)) {
continue;
}
podState.increasePacketNumber(2);
return response;
}
throw new CommunicationException(CommunicationException.Type.TIMEOUT);
}
}

View file

@ -0,0 +1,664 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
import java.util.EnumSet;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AcknowledgeAlertsAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AssignAddressAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.CancelDeliveryAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigurePodAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.DeactivatePodAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetPodInfoAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetStatusAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.InsertCannulaAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.PrimeAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetTempBasalAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CommunicationException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalDeliveryStatusException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalSetupProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.NonceOutOfSyncException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.PodFaultException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.CancelDeliveryCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
import info.nightscout.androidaps.utils.sharedPreferences.SP;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.SingleSubject;
public class OmnipodManager {
private static final int ACTION_VERIFICATION_TRIES = 3;
protected final OmnipodCommunicationManager communicationService;
private final PodStateChangedHandler podStateChangedHandler;
protected PodSessionState podState;
private ActiveBolusData activeBolusData;
private final Object bolusDataMutex = new Object();
//private HasAndroidInjector injector;
AAPSLogger aapsLogger;
SP sp;
public OmnipodManager(//HasAndroidInjector injector,
AAPSLogger aapsLogger,
SP sp,
OmnipodCommunicationManager communicationService,
PodSessionState podState,
PodStateChangedHandler podStateChangedHandler) {
// this.injector = injector;
// this.injector.androidInjector().inject(this);
if (communicationService == null) {
throw new IllegalArgumentException("Communication service cannot be null");
}
this.aapsLogger = aapsLogger;
this.sp = sp;
this.communicationService = communicationService;
if (podState != null) {
podState.setStateChangedHandler(podStateChangedHandler);
}
this.podState = podState;
this.podStateChangedHandler = podStateChangedHandler;
}
// public OmnipodManager(HasAndroidInjector injector,
// OmnipodCommunicationManager communicationService,
// PodSessionState podState) {
// this(injector, communicationService, podState, null);
// }
public synchronized Single<SetupActionResult> pairAndPrime() {
logStartingCommandExecution("pairAndPrime");
try {
if (podState == null) {
podState = communicationService.executeAction(
new AssignAddressAction(podStateChangedHandler));
} else if (SetupProgress.PRIMING.isBefore(podState.getSetupProgress())) {
throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress());
}
if (SetupProgress.ADDRESS_ASSIGNED.equals(podState.getSetupProgress())) {
communicationService.executeAction(new ConfigurePodAction(podState));
}
communicationService.executeAction(new PrimeAction(new PrimeService(), podState));
} finally {
logCommandExecutionFinished("pairAndPrime");
}
long delayInSeconds = calculateBolusDuration(OmnipodConst.POD_PRIME_BOLUS_UNITS, OmnipodConst.POD_PRIMING_DELIVERY_RATE).getStandardSeconds();
return Single.timer(delayInSeconds, TimeUnit.SECONDS) //
.map(o -> verifySetupAction(statusResponse ->
PrimeAction.updatePrimingStatus(podState, statusResponse, aapsLogger), SetupProgress.PRIMING_FINISHED)) //
.observeOn(Schedulers.io());
}
public synchronized Single<SetupActionResult> insertCannula(BasalSchedule basalSchedule) {
if (podState == null || podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, podState == null ? null : podState.getSetupProgress());
} else if (podState.getSetupProgress().isAfter(SetupProgress.CANNULA_INSERTING)) {
throw new IllegalSetupProgressException(SetupProgress.CANNULA_INSERTING, podState.getSetupProgress());
}
logStartingCommandExecution("insertCannula [basalSchedule=" + basalSchedule + "]");
try {
communicationService.executeAction(new InsertCannulaAction(new InsertCannulaService(), podState, basalSchedule));
} finally {
logCommandExecutionFinished("insertCannula");
}
long delayInSeconds = calculateBolusDuration(OmnipodConst.POD_CANNULA_INSERTION_BOLUS_UNITS, OmnipodConst.POD_CANNULA_INSERTION_DELIVERY_RATE).getStandardSeconds();
return Single.timer(delayInSeconds, TimeUnit.SECONDS) //
.map(o -> verifySetupAction(statusResponse ->
InsertCannulaAction.updateCannulaInsertionStatus(podState, statusResponse, aapsLogger), SetupProgress.COMPLETED)) //
.observeOn(Schedulers.io());
}
public synchronized StatusResponse getPodStatus() {
if (podState == null) {
throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, null);
}
logStartingCommandExecution("getPodStatus");
try {
return communicationService.executeAction(new GetStatusAction(podState));
} finally {
logCommandExecutionFinished("getPodStatus");
}
}
public synchronized PodInfoResponse getPodInfo(PodInfoType podInfoType) {
assertReadyForDelivery();
logStartingCommandExecution("getPodInfo");
try {
return communicationService.executeAction(new GetPodInfoAction(podState, podInfoType));
} finally {
logCommandExecutionFinished("getPodInfo");
}
}
public synchronized StatusResponse acknowledgeAlerts() {
assertReadyForDelivery();
logStartingCommandExecution("acknowledgeAlerts");
try {
return executeAndVerify(() -> communicationService.executeAction(new AcknowledgeAlertsAction(podState, podState.getActiveAlerts())));
} finally {
logCommandExecutionFinished("acknowledgeAlerts");
}
}
// CAUTION: cancels all delivery
// CAUTION: suspends and then resumes delivery. An OmnipodException[certainFailure=false] indicates that the pod is or might be suspended
public synchronized StatusResponse setBasalSchedule(BasalSchedule schedule, boolean acknowledgementBeep) {
assertReadyForDelivery();
logStartingCommandExecution("setBasalSchedule [basalSchedule=" + schedule + ", acknowledgementBeep=" + acknowledgementBeep + "]");
try {
cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeep);
} catch (Exception ex) {
logCommandExecutionFinished("setBasalSchedule");
throw ex;
}
try {
try {
return executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podState, schedule,
false, podState.getScheduleOffset(), acknowledgementBeep)));
} catch (OmnipodException ex) {
// Treat all exceptions as uncertain failures, because all delivery has been suspended here.
// Setting this to an uncertain failure will enable for the user to get an appropriate warning
ex.setCertainFailure(false);
throw ex;
}
} finally {
logCommandExecutionFinished("setBasalSchedule");
}
}
// CAUTION: cancels temp basal and then sets new temp basal. An OmnipodException[certainFailure=false] indicates that the pod might have cancelled the previous temp basal, but did not set a new temp basal
public synchronized StatusResponse setTemporaryBasal(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep) {
assertReadyForDelivery();
logStartingCommandExecution("setTemporaryBasal [rate=" + rate + ", duration=" + duration + ", acknowledgementBeep=" + acknowledgementBeep + ", completionBeep=" + completionBeep + "]");
try {
cancelDelivery(EnumSet.of(DeliveryType.TEMP_BASAL), acknowledgementBeep);
} catch (Exception ex) {
logCommandExecutionFinished("setTemporaryBasal");
throw ex;
}
try {
return executeAndVerify(() -> communicationService.executeAction(new SetTempBasalAction(
podState, rate, duration,
acknowledgementBeep, completionBeep)));
} catch (OmnipodException ex) {
// Treat all exceptions as uncertain failures, because all delivery has been suspended here.
// Setting this to an uncertain failure will enable for the user to get an appropriate warning
ex.setCertainFailure(false);
throw ex;
} finally {
logCommandExecutionFinished("setTemporaryBasal");
}
}
public synchronized void cancelTemporaryBasal(boolean acknowledgementBeep) {
cancelDelivery(EnumSet.of(DeliveryType.TEMP_BASAL), acknowledgementBeep);
}
private synchronized StatusResponse cancelDelivery(EnumSet<DeliveryType> deliveryTypes, boolean acknowledgementBeep) {
assertReadyForDelivery();
logStartingCommandExecution("cancelDelivery [deliveryTypes=" + deliveryTypes + ", acknowledgementBeep=" + acknowledgementBeep + "]");
try {
return executeAndVerify(() -> {
StatusResponse statusResponse = communicationService.executeAction(new CancelDeliveryAction(podState, deliveryTypes, acknowledgementBeep));
aapsLogger.info(LTag.PUMPBTCOMM, "Status response after cancel delivery[types={}]: {}", deliveryTypes.toString(), statusResponse.toString());
return statusResponse;
});
} finally {
logCommandExecutionFinished("cancelDelivery");
}
}
// Returns a SingleSubject that returns when the bolus has finished.
// When a bolus is cancelled, it will return after cancellation and report the estimated units delivered
// Only throws OmnipodException[certainFailure=false]
public synchronized BolusCommandResult bolus(Double units, boolean acknowledgementBeep, boolean completionBeep, BolusProgressIndicationConsumer progressIndicationConsumer) {
assertReadyForDelivery();
logStartingCommandExecution("bolus [units=" + units + ", acknowledgementBeep=" + acknowledgementBeep + ", completionBeep=" + completionBeep + "]");
CommandDeliveryStatus commandDeliveryStatus = CommandDeliveryStatus.SUCCESS;
try {
executeAndVerify(() -> communicationService.executeAction(new BolusAction(podState, units, acknowledgementBeep, completionBeep)));
} catch (OmnipodException ex) {
if (ex.isCertainFailure()) {
throw ex;
}
// Catch uncertain exceptions as we still want to report bolus progress indication
aapsLogger.error(LTag.PUMPBTCOMM, "Caught exception[certainFailure=false] in bolus", ex);
commandDeliveryStatus = CommandDeliveryStatus.UNCERTAIN_FAILURE;
} finally {
logCommandExecutionFinished("bolus");
}
DateTime startDate = DateTime.now().minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION);
CompositeDisposable disposables = new CompositeDisposable();
Duration bolusDuration = calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE);
Duration estimatedRemainingBolusDuration = bolusDuration.minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION);
if (progressIndicationConsumer != null) {
int numberOfProgressReports = Math.max(20, Math.min(100, (int) Math.ceil(units) * 10));
long progressReportInterval = estimatedRemainingBolusDuration.getMillis() / numberOfProgressReports;
disposables.add(Flowable.intervalRange(0, numberOfProgressReports + 1, 0, progressReportInterval, TimeUnit.MILLISECONDS) //
.observeOn(Schedulers.io()) //
.subscribe(count -> {
int percentage = (int) ((double) count / numberOfProgressReports * 100);
double estimatedUnitsDelivered = activeBolusData == null ? 0 : activeBolusData.estimateUnitsDelivered();
progressIndicationConsumer.accept(estimatedUnitsDelivered, percentage);
}));
}
SingleSubject<BolusDeliveryResult> bolusCompletionSubject = SingleSubject.create();
synchronized (bolusDataMutex) {
activeBolusData = new ActiveBolusData(units, startDate, bolusCompletionSubject, disposables);
}
disposables.add(Completable.complete() //
.delay(estimatedRemainingBolusDuration.getMillis() + 250, TimeUnit.MILLISECONDS) //
.observeOn(Schedulers.io()) //
.doOnComplete(() -> {
synchronized (bolusDataMutex) {
double unitsNotDelivered = 0.0d;
for (int i = 0; i < ACTION_VERIFICATION_TRIES; i++) {
try {
// Retrieve a status response in order to update the pod state
StatusResponse statusResponse = getPodStatus();
if (statusResponse.getDeliveryStatus().isBolusing()) {
throw new IllegalDeliveryStatusException(DeliveryStatus.NORMAL, statusResponse.getDeliveryStatus());
} else {
break;
}
} catch (PodFaultException ex) {
// Substract units not delivered in case of a Pod failure
unitsNotDelivered = ex.getFaultEvent().getInsulinNotDelivered();
aapsLogger.debug(LTag.PUMPBTCOMM, "Caught PodFaultException in bolus completion verification", ex);
break;
} catch (Exception ex) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Ignoring exception in bolus completion verification", ex);
}
}
if (hasActiveBolus()) {
activeBolusData.bolusCompletionSubject.onSuccess(new BolusDeliveryResult(units - unitsNotDelivered));
activeBolusData = null;
}
}
})
.subscribe());
return new BolusCommandResult(commandDeliveryStatus, bolusCompletionSubject);
}
public synchronized void cancelBolus(boolean acknowledgementBeep) {
assertReadyForDelivery();
synchronized (bolusDataMutex) {
if (activeBolusData == null) {
throw new IllegalDeliveryStatusException(DeliveryStatus.BOLUS_IN_PROGRESS, podState.getLastDeliveryStatus());
}
logStartingCommandExecution("cancelBolus [acknowledgementBeep=" + acknowledgementBeep + "]");
try {
StatusResponse statusResponse = cancelDelivery(EnumSet.of(DeliveryType.BOLUS), acknowledgementBeep);
discardActiveBolusData(statusResponse.getInsulinNotDelivered());
} catch (PodFaultException ex) {
discardActiveBolusData(ex.getFaultEvent().getInsulinNotDelivered());
throw ex;
} finally {
logCommandExecutionFinished("cancelBolus");
}
}
}
private void discardActiveBolusData(double unitsNotDelivered) {
synchronized (bolusDataMutex) {
activeBolusData.getDisposables().dispose();
activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(activeBolusData.getUnits() - unitsNotDelivered));
activeBolusData = null;
}
}
public synchronized void suspendDelivery(boolean acknowledgementBeep) {
cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeep);
}
// Same as setting basal schedule, but without suspending delivery first
public synchronized StatusResponse resumeDelivery(boolean acknowledgementBeep) {
assertReadyForDelivery();
logStartingCommandExecution("resumeDelivery");
try {
return executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podState, podState.getBasalSchedule(),
false, podState.getScheduleOffset(), acknowledgementBeep)));
} finally {
logCommandExecutionFinished("resumeDelivery");
}
}
// CAUTION: cancels all delivery
// CAUTION: suspends and then resumes delivery. An OmnipodException[certainFailure=false] indicates that the pod is or might be suspended
public synchronized void setTime(boolean acknowledgementBeeps) {
assertReadyForDelivery();
logStartingCommandExecution("setTime [acknowledgementBeeps=" + acknowledgementBeeps + "]");
try {
cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeeps);
} catch (Exception ex) {
logCommandExecutionFinished("setTime");
throw ex;
}
DateTimeZone oldTimeZone = podState.getTimeZone();
try {
// Joda seems to cache the default time zone, so we use the JVM's
DateTimeZone.setDefault(DateTimeZone.forTimeZone(TimeZone.getDefault()));
podState.setTimeZone(DateTimeZone.getDefault());
setBasalSchedule(podState.getBasalSchedule(), acknowledgementBeeps);
} catch (OmnipodException ex) {
// Treat all exceptions as uncertain failures, because all delivery has been suspended here.
// Setting this to an uncertain failure will enable for the user to get an appropriate warning
podState.setTimeZone(oldTimeZone);
ex.setCertainFailure(false);
throw ex;
} finally {
logCommandExecutionFinished("setTime");
}
}
public synchronized void deactivatePod() {
if (podState == null) {
throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, null);
}
logStartingCommandExecution("deactivatePod");
// Try to get pulse log for diagnostics
// FIXME replace by storing to file
try {
PodInfoResponse podInfoResponse = communicationService.executeAction(new GetPodInfoAction(podState, PodInfoType.RECENT_PULSE_LOG));
PodInfoRecentPulseLog pulseLogInfo = podInfoResponse.getPodInfo();
aapsLogger.info(LTag.PUMPBTCOMM, "Retrieved pulse log from the pod: {}", pulseLogInfo.toString());
} catch (Exception ex) {
aapsLogger.warn(LTag.PUMPBTCOMM, "Failed to retrieve pulse log from the pod", ex);
}
try {
// Always send acknowledgement beeps here. Matches the PDM's behavior
communicationService.executeAction(new DeactivatePodAction(podState, true));
} catch (PodFaultException ex) {
aapsLogger.info(LTag.PUMPBTCOMM, "Ignoring PodFaultException in deactivatePod", ex);
} finally {
logCommandExecutionFinished("deactivatePod");
}
resetPodState(false);
}
public void resetPodState(boolean forcedByUser) {
aapsLogger.warn(LTag.PUMPBTCOMM, "resetPodState has been called. forcedByUser={}", forcedByUser);
podState = null;
sp.remove(OmnipodConst.Prefs.PodState);
}
public OmnipodCommunicationManager getCommunicationService() {
return communicationService;
}
public DateTime getTime() {
return podState.getTime();
}
public boolean isReadyForDelivery() {
return podState != null && podState.getSetupProgress() == SetupProgress.COMPLETED;
}
public boolean hasActiveBolus() {
synchronized (bolusDataMutex) {
return activeBolusData != null;
}
}
// FIXME this is dirty, we should not expose the original pod state
public PodSessionState getPodState() {
return this.podState;
}
public String getPodStateAsString() {
return podState == null ? "null" : podState.toString();
}
// Only works for commands with nonce resyncable message blocks
// FIXME method is too big, needs refactoring
private StatusResponse executeAndVerify(VerifiableAction runnable) {
try {
return runnable.run();
} catch (Exception originalException) {
if (isCertainFailure(originalException)) {
throw originalException;
} else {
aapsLogger.warn(LTag.PUMPBTCOMM, "Caught exception in executeAndVerify. Verifying command by using cancel none command to verify nonce", originalException);
try {
logStartingCommandExecution("verifyCommand");
StatusResponse statusResponse = communicationService.sendCommand(StatusResponse.class, podState,
new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.NO_BEEP, DeliveryType.NONE), false);
aapsLogger.info(LTag.PUMPBTCOMM, "Command status resolved to SUCCESS. Status response after cancelDelivery[types=DeliveryType.NONE]: {}", statusResponse);
return statusResponse;
} catch (NonceOutOfSyncException verificationException) {
aapsLogger.error(LTag.PUMPBTCOMM, "Command resolved to FAILURE (CERTAIN_FAILURE)", verificationException);
if (originalException instanceof OmnipodException) {
((OmnipodException) originalException).setCertainFailure(true);
throw originalException;
} else {
OmnipodException newException = new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, originalException);
newException.setCertainFailure(true);
throw newException;
}
} catch (Exception verificationException) {
aapsLogger.error(LTag.PUMPBTCOMM, "Command unresolved (UNCERTAIN_FAILURE)", verificationException);
throw originalException;
} finally {
logCommandExecutionFinished("verifyCommand");
}
}
}
}
private void assertReadyForDelivery() {
if (!isReadyForDelivery()) {
throw new IllegalSetupProgressException(SetupProgress.COMPLETED, podState == null ? null : podState.getSetupProgress());
}
}
private SetupActionResult verifySetupAction(StatusResponseConsumer setupActionResponseHandler, SetupProgress expectedSetupProgress) {
SetupActionResult result = null;
for (int i = 0; ACTION_VERIFICATION_TRIES > i; i++) {
try {
StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState));
setupActionResponseHandler.accept(delayedStatusResponse);
if (podState.getSetupProgress().equals(expectedSetupProgress)) {
result = new SetupActionResult(SetupActionResult.ResultType.SUCCESS);
break;
} else {
result = new SetupActionResult(SetupActionResult.ResultType.FAILURE) //
.setupProgress(podState.getSetupProgress());
break;
}
} catch (Exception ex) {
result = new SetupActionResult(SetupActionResult.ResultType.VERIFICATION_FAILURE) //
.exception(ex);
}
}
return result;
}
private void logStartingCommandExecution(String action) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Starting command execution for action: " + action);
}
private void logCommandExecutionFinished(String action) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Command execution finished for action: " + action);
}
private static Duration calculateBolusDuration(double units, double deliveryRate) {
return Duration.standardSeconds((long) Math.ceil(units / deliveryRate));
}
public static Duration calculateBolusDuration(double units) {
return calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE);
}
public static boolean isCertainFailure(Exception ex) {
return ex instanceof OmnipodException && ((OmnipodException) ex).isCertainFailure();
}
public static class BolusCommandResult {
private final CommandDeliveryStatus commandDeliveryStatus;
private final SingleSubject<BolusDeliveryResult> deliveryResultSubject;
public BolusCommandResult(CommandDeliveryStatus commandDeliveryStatus, SingleSubject<BolusDeliveryResult> deliveryResultSubject) {
this.commandDeliveryStatus = commandDeliveryStatus;
this.deliveryResultSubject = deliveryResultSubject;
}
public CommandDeliveryStatus getCommandDeliveryStatus() {
return commandDeliveryStatus;
}
public SingleSubject<BolusDeliveryResult> getDeliveryResultSubject() {
return deliveryResultSubject;
}
}
public static class BolusDeliveryResult {
private final double unitsDelivered;
public BolusDeliveryResult(double unitsDelivered) {
this.unitsDelivered = unitsDelivered;
}
public double getUnitsDelivered() {
return unitsDelivered;
}
}
public enum CommandDeliveryStatus {
SUCCESS,
CERTAIN_FAILURE,
UNCERTAIN_FAILURE
}
// TODO replace with Consumer when our min API level >= 24
@FunctionalInterface
private interface StatusResponseConsumer {
void accept(StatusResponse statusResponse);
}
private static class ActiveBolusData {
private final double units;
private volatile DateTime startDate;
private volatile SingleSubject<BolusDeliveryResult> bolusCompletionSubject;
private volatile CompositeDisposable disposables;
private ActiveBolusData(double units, DateTime startDate, SingleSubject<BolusDeliveryResult> bolusCompletionSubject, CompositeDisposable disposables) {
this.units = units;
this.startDate = startDate;
this.bolusCompletionSubject = bolusCompletionSubject;
this.disposables = disposables;
}
public double getUnits() {
return units;
}
public DateTime getStartDate() {
return startDate;
}
public CompositeDisposable getDisposables() {
return disposables;
}
public SingleSubject<BolusDeliveryResult> getBolusCompletionSubject() {
return bolusCompletionSubject;
}
public double estimateUnitsDelivered() {
long elapsedMillis = new Duration(startDate, DateTime.now()).getMillis();
long totalDurationMillis = (long) (units / OmnipodConst.POD_BOLUS_DELIVERY_RATE * 1000);
double factor = (double) elapsedMillis / totalDurationMillis;
double estimatedUnits = Math.min(1D, factor) * units;
int roundingDivisor = (int) (1 / OmnipodConst.POD_PULSE_SIZE);
return (double) Math.round(estimatedUnits * roundingDivisor) / roundingDivisor;
}
}
// Could be replaced with Supplier<StatusResponse> when min API level >= 24
@FunctionalInterface
private interface VerifiableAction {
StatusResponse run();
}
}

View file

@ -0,0 +1,61 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
public class SetupActionResult {
private final ResultType resultType;
private String message;
private Exception exception;
private SetupProgress setupProgress;
public SetupActionResult(ResultType resultType) {
this.resultType = resultType;
}
public SetupActionResult message(String message) {
this.message = message;
return this;
}
public SetupActionResult exception(Exception ex) {
exception = ex;
return this;
}
public SetupActionResult setupProgress(SetupProgress setupProgress) {
this.setupProgress = setupProgress;
return this;
}
public ResultType getResultType() {
return resultType;
}
public String getMessage() {
return message;
}
public Exception getException() {
return exception;
}
public SetupProgress getSetupProgress() {
return setupProgress;
}
public enum ResultType {
SUCCESS(true),
VERIFICATION_FAILURE(false),
FAILURE(false);
private final boolean success;
ResultType(boolean success) {
this.success = success;
}
public boolean isSuccess() {
return success;
}
}
}

View file

@ -0,0 +1,39 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import java.util.Collections;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AcknowledgeAlertsCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class AcknowledgeAlertsAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final AlertSet alerts;
public AcknowledgeAlertsAction(PodSessionState podState, AlertSet alerts) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (alerts == null) {
throw new ActionInitializationException("Alert set can not be null");
} else if (alerts.size() == 0) {
throw new ActionInitializationException("Alert set can not be empty");
}
this.podState = podState;
this.alerts = alerts;
}
public AcknowledgeAlertsAction(PodSessionState podState, AlertSlot alertSlot) {
this(podState, new AlertSet(Collections.singletonList(alertSlot)));
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
return communicationService.sendCommand(StatusResponse.class, podState,
new AcknowledgeAlertsCommand(podState.getCurrentNonce(), alerts));
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.DateTimeZone;
import java.util.Collections;
import java.util.Random;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AssignAddressCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class AssignAddressAction implements OmnipodAction<PodSessionState> {
private final int address;
private final PodStateChangedHandler podStateChangedHandler;
public AssignAddressAction(PodStateChangedHandler podStateChangedHandler) {
this.address = generateRandomAddress();
this.podStateChangedHandler = podStateChangedHandler;
}
private static int generateRandomAddress() {
return 0x1f000000 | (new Random().nextInt() & 0x000fffff);
}
@Override
public PodSessionState execute(OmnipodCommunicationManager communicationService) {
PodSetupState setupState = new PodSetupState(address, 0x00, 0x00);
AssignAddressCommand assignAddress = new AssignAddressCommand(setupState.getAddress());
OmnipodMessage assignAddressMessage = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
Collections.singletonList(assignAddress), setupState.getMessageNumber());
VersionResponse assignAddressResponse = communicationService.exchangeMessages(VersionResponse.class, setupState, assignAddressMessage,
OmnipodConst.DEFAULT_ADDRESS, setupState.getAddress());
DateTimeZone timeZone = DateTimeZone.getDefault();
PodSessionState podState = new PodSessionState(timeZone, address, assignAddressResponse.getPiVersion(),
assignAddressResponse.getPmVersion(), assignAddressResponse.getLot(), assignAddressResponse.getTid(),
setupState.getPacketNumber(), 0x00, communicationService.injector); // At this point, for an unknown reason, the pod starts counting messages from 0 again
podState.setStateChangedHandler(podStateChangedHandler);
return podState;
}
}

View file

@ -0,0 +1,53 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.Duration;
import java.util.Arrays;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BolusExtraCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class BolusAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final double units;
private final Duration timeBetweenPulses;
private final boolean acknowledgementBeep;
private final boolean completionBeep;
public BolusAction(PodSessionState podState, double units, Duration timeBetweenPulses,
boolean acknowledgementBeep, boolean completionBeep) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (timeBetweenPulses == null) {
throw new ActionInitializationException("Time between pulses cannot be null");
}
this.podState = podState;
this.units = units;
this.timeBetweenPulses = timeBetweenPulses;
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
}
public BolusAction(PodSessionState podState, double units, boolean acknowledgementBeep, boolean completionBeep) {
this(podState, units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep);
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
BolusDeliverySchedule bolusDeliverySchedule = new BolusDeliverySchedule(units, timeBetweenPulses);
SetInsulinScheduleCommand setInsulinScheduleCommand = new SetInsulinScheduleCommand(
podState.getCurrentNonce(), bolusDeliverySchedule);
BolusExtraCommand bolusExtraCommand = new BolusExtraCommand(units, timeBetweenPulses,
acknowledgementBeep, completionBeep);
OmnipodMessage primeBolusMessage = new OmnipodMessage(podState.getAddress(),
Arrays.asList(setInsulinScheduleCommand, bolusExtraCommand), podState.getMessageNumber());
return communicationService.exchangeMessages(StatusResponse.class, podState, primeBolusMessage);
}
}

View file

@ -0,0 +1,56 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.CancelDeliveryCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class CancelDeliveryAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final EnumSet<DeliveryType> deliveryTypes;
private final boolean acknowledgementBeep;
public CancelDeliveryAction(PodSessionState podState, EnumSet<DeliveryType> deliveryTypes,
boolean acknowledgementBeep) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (deliveryTypes == null) {
throw new ActionInitializationException("Delivery types cannot be null");
}
this.podState = podState;
this.deliveryTypes = deliveryTypes;
this.acknowledgementBeep = acknowledgementBeep;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
List<MessageBlock> messageBlocks = new ArrayList<>();
if (acknowledgementBeep && deliveryTypes.size() > 1) {
// Workaround for strange beep behaviour when cancelling multiple delivery types
List<DeliveryType> deliveryTypeList = new ArrayList<>(deliveryTypes);
EnumSet<DeliveryType> deliveryTypeWithBeep = EnumSet.of(deliveryTypeList.remove(deliveryTypeList.size() - 1));
EnumSet<DeliveryType> deliveryTypesWithoutBeep = EnumSet.copyOf(deliveryTypeList);
messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.NO_BEEP, deliveryTypesWithoutBeep));
messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.BEEP, deliveryTypeWithBeep));
} else {
messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(),
acknowledgementBeep && deliveryTypes.size() == 1 ? BeepType.BEEP : BeepType.NO_BEEP, deliveryTypes));
}
return communicationService.exchangeMessages(StatusResponse.class, podState,
new OmnipodMessage(podState.getAddress(), messageBlocks, podState.getMessageNumber()));
}
}

View file

@ -0,0 +1,36 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigureAlertsCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class ConfigureAlertsAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final List<AlertConfiguration> alertConfigurations;
public ConfigureAlertsAction(PodSessionState podState, List<AlertConfiguration> alertConfigurations) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (alertConfigurations == null) {
throw new ActionInitializationException("Alert configurations cannot be null");
}
this.podState = podState;
this.alertConfigurations = alertConfigurations;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
ConfigureAlertsCommand configureAlertsCommand = new ConfigureAlertsCommand(podState.getCurrentNonce(), alertConfigurations);
StatusResponse statusResponse = communicationService.sendCommand(StatusResponse.class, podState, configureAlertsCommand);
for (AlertConfiguration alertConfiguration : alertConfigurations) {
podState.putConfiguredAlert(alertConfiguration.getAlertSlot(), alertConfiguration.getAlertType());
}
return statusResponse;
}
}

View file

@ -0,0 +1,59 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.DateTime;
import java.util.Collections;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigurePodCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalPacketTypeException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalPodProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalSetupProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class ConfigurePodAction implements OmnipodAction<VersionResponse> {
private final PodSessionState podState;
public ConfigurePodAction(PodSessionState podState) {
this.podState = podState;
}
@Override
public VersionResponse execute(OmnipodCommunicationManager communicationService) {
if (!podState.getSetupProgress().equals(SetupProgress.ADDRESS_ASSIGNED)) {
throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress());
}
DateTime activationDate = DateTime.now(podState.getTimeZone());
ConfigurePodCommand configurePodCommand = new ConfigurePodCommand(podState.getAddress(), activationDate,
podState.getLot(), podState.getTid());
OmnipodMessage message = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
Collections.singletonList(configurePodCommand), podState.getMessageNumber());
VersionResponse configurePodResponse;
try {
configurePodResponse = communicationService.exchangeMessages(VersionResponse.class, podState,
message, OmnipodConst.DEFAULT_ADDRESS, podState.getAddress());
} catch (IllegalPacketTypeException ex) {
if (PacketType.ACK.equals(ex.getActual())) {
// Pod is already configured
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
return null;
}
throw ex;
}
if (configurePodResponse.getPodProgressStatus() != PodProgressStatus.PAIRING_SUCCESS) {
throw new IllegalPodProgressException(PodProgressStatus.PAIRING_SUCCESS, configurePodResponse.getPodProgressStatus());
}
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
return configurePodResponse;
}
}

View file

@ -0,0 +1,38 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import java.util.EnumSet;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.DeactivatePodCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.PodFaultException;
public class DeactivatePodAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final boolean acknowledgementBeep;
public DeactivatePodAction(PodSessionState podState, boolean acknowledgementBeep) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
this.podState = podState;
this.acknowledgementBeep = acknowledgementBeep;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
if (!podState.isSuspended() && !podState.hasFaultEvent()) {
try {
communicationService.executeAction(new CancelDeliveryAction(podState,
EnumSet.allOf(DeliveryType.class), acknowledgementBeep));
} catch(PodFaultException ex) {
// Ignore
}
}
return communicationService.sendCommand(StatusResponse.class, podState, new DeactivatePodCommand(podState.getCurrentNonce()));
}
}

View file

@ -0,0 +1,29 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class GetPodInfoAction implements OmnipodAction<PodInfoResponse> {
private final PodSessionState podState;
private final PodInfoType podInfoType;
public GetPodInfoAction(PodSessionState podState, PodInfoType podInfoType) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (podInfoType == null) {
throw new ActionInitializationException("Pod info type cannot be null");
}
this.podState = podState;
this.podInfoType = podInfoType;
}
@Override
public PodInfoResponse execute(OmnipodCommunicationManager communicationService) {
return communicationService.sendCommand(PodInfoResponse.class, podState, new GetStatusCommand(podInfoType));
}
}

View file

@ -0,0 +1,24 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class GetStatusAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
public GetStatusAction(PodSessionState podState) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
this.podState = podState;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
return communicationService.sendCommand(StatusResponse.class, podState, new GetStatusCommand(PodInfoType.NORMAL));
}
}

View file

@ -0,0 +1,71 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalSetupProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
public class InsertCannulaAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final InsertCannulaService service;
private final BasalSchedule initialBasalSchedule;
public InsertCannulaAction(InsertCannulaService insertCannulaService, PodSessionState podState, BasalSchedule initialBasalSchedule) {
if (insertCannulaService == null) {
throw new ActionInitializationException("Insert cannula service cannot be null");
}
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (initialBasalSchedule == null) {
throw new ActionInitializationException("Initial basal schedule cannot be null");
}
this.service = insertCannulaService;
this.podState = podState;
this.initialBasalSchedule = initialBasalSchedule;
}
public static void updateCannulaInsertionStatus(PodSessionState podState, StatusResponse statusResponse, AAPSLogger aapsLogger) {
if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING) &&
statusResponse.getPodProgressStatus().isReadyForDelivery()) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Updating SetupProgress from CANNULA_INSERTING to COMPLETED");
podState.setSetupProgress(SetupProgress.COMPLETED);
}
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, podState.getSetupProgress());
}
if (podState.getSetupProgress().isBefore(SetupProgress.INITIAL_BASAL_SCHEDULE_SET)) {
service.programInitialBasalSchedule(communicationService, podState, initialBasalSchedule);
podState.setSetupProgress(SetupProgress.INITIAL_BASAL_SCHEDULE_SET);
}
if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_INSERT_CANNULA)) {
service.executeExpirationRemindersAlertCommand(communicationService, podState);
podState.setSetupProgress(SetupProgress.STARTING_INSERT_CANNULA);
}
if (podState.getSetupProgress().isBefore(SetupProgress.CANNULA_INSERTING)) {
StatusResponse statusResponse = service.executeInsertionBolusCommand(communicationService, podState);
podState.setSetupProgress(SetupProgress.CANNULA_INSERTING);
return statusResponse;
} else if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING)) {
// Check status
StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState));
updateCannulaInsertionStatus(podState, statusResponse, communicationService.aapsLogger);
return statusResponse;
} else {
throw new IllegalSetupProgressException(null, podState.getSetupProgress());
}
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
public interface OmnipodAction<T> {
T execute(OmnipodCommunicationManager communicationService);
}

View file

@ -0,0 +1,61 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalSetupProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
public class PrimeAction implements OmnipodAction<StatusResponse> {
private final PrimeService service;
private final PodSessionState podState;
public PrimeAction(PrimeService primeService, PodSessionState podState) {
if (primeService == null) {
throw new ActionInitializationException("Prime service cannot be null");
}
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
this.service = primeService;
this.podState = podState;
}
public static void updatePrimingStatus(PodSessionState podState, StatusResponse statusResponse, AAPSLogger aapsLogger) {
if (podState.getSetupProgress().equals(SetupProgress.PRIMING) && statusResponse.getPodProgressStatus().equals(PodProgressStatus.READY_FOR_BASAL_SCHEDULE)) {
aapsLogger.debug(LTag.PUMPBTCOMM, "Updating SetupProgress from PRIMING to PRIMING_FINISHED");
podState.setSetupProgress(SetupProgress.PRIMING_FINISHED);
}
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
if (podState.getSetupProgress().isBefore(SetupProgress.POD_CONFIGURED)) {
throw new IllegalSetupProgressException(SetupProgress.POD_CONFIGURED, podState.getSetupProgress());
}
if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_PRIME)) {
service.executeDisableTab5Sub16FaultConfigCommand(communicationService, podState);
service.executeFinishSetupReminderAlertCommand(communicationService, podState);
podState.setSetupProgress(SetupProgress.STARTING_PRIME);
}
if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING)) {
StatusResponse statusResponse = service.executePrimeBolusCommand(communicationService, podState);
podState.setSetupProgress(SetupProgress.PRIMING);
return statusResponse;
} else if (podState.getSetupProgress().equals(SetupProgress.PRIMING)) {
// Check status
StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState));
updatePrimingStatus(podState, statusResponse, communicationService.aapsLogger);
return statusResponse;
} else {
throw new IllegalSetupProgressException(null, podState.getSetupProgress());
}
}
}

View file

@ -0,0 +1,53 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.Duration;
import java.util.Arrays;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BasalScheduleExtraCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class SetBasalScheduleAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final BasalSchedule basalSchedule;
private final boolean confidenceReminder;
private final Duration scheduleOffset;
private final boolean acknowledgementBeep;
public SetBasalScheduleAction(PodSessionState podState, BasalSchedule basalSchedule,
boolean confidenceReminder, Duration scheduleOffset, boolean acknowledgementBeep) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (basalSchedule == null) {
throw new ActionInitializationException("Basal schedule cannot be null");
}
if (scheduleOffset == null) {
throw new ActionInitializationException("Schedule offset cannot be null");
}
this.podState = podState;
this.basalSchedule = basalSchedule;
this.confidenceReminder = confidenceReminder;
this.scheduleOffset = scheduleOffset;
this.acknowledgementBeep = acknowledgementBeep;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
SetInsulinScheduleCommand setBasal = new SetInsulinScheduleCommand(podState.getCurrentNonce(), basalSchedule, scheduleOffset);
BasalScheduleExtraCommand extraCommand = new BasalScheduleExtraCommand(basalSchedule, scheduleOffset,
acknowledgementBeep, confidenceReminder, Duration.ZERO);
OmnipodMessage basalMessage = new OmnipodMessage(podState.getAddress(), Arrays.asList(setBasal, extraCommand),
podState.getMessageNumber());
StatusResponse statusResponse = communicationService.exchangeMessages(StatusResponse.class, podState, basalMessage);
podState.setBasalSchedule(basalSchedule);
return statusResponse;
}
}

View file

@ -0,0 +1,48 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.Duration;
import java.util.Arrays;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.TempBasalExtraCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.ActionInitializationException;
public class SetTempBasalAction implements OmnipodAction<StatusResponse> {
private final PodSessionState podState;
private final double rate;
private final Duration duration;
private final boolean acknowledgementBeep;
private final boolean completionBeep;
public SetTempBasalAction(PodSessionState podState, double rate, Duration duration,
boolean acknowledgementBeep, boolean completionBeep) {
if (podState == null) {
throw new ActionInitializationException("Pod state cannot be null");
}
if (duration == null) {
throw new ActionInitializationException("Duration cannot be null");
}
this.podState = podState;
this.rate = rate;
this.duration = duration;
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
}
@Override
public StatusResponse execute(OmnipodCommunicationManager communicationService) {
List<MessageBlock> messageBlocks = Arrays.asList( //
new SetInsulinScheduleCommand(podState.getCurrentNonce(), rate, duration),
new TempBasalExtraCommand(rate, duration, acknowledgementBeep, completionBeep, Duration.ZERO));
OmnipodMessage message = new OmnipodMessage(podState.getAddress(), messageBlocks, podState.getMessageNumber());
return communicationService.exchangeMessages(StatusResponse.class, podState, message);
}
}

View file

@ -0,0 +1,59 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.util.Arrays;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class InsertCannulaService {
public StatusResponse programInitialBasalSchedule(OmnipodCommunicationManager communicationService,
PodSessionState podState, BasalSchedule basalSchedule) {
return communicationService.executeAction(new SetBasalScheduleAction(podState, basalSchedule,
true, podState.getScheduleOffset(), false));
}
public StatusResponse executeExpirationRemindersAlertCommand(OmnipodCommunicationManager communicationService,
PodSessionState podState) {
AlertConfiguration lowReservoirAlertConfiguration = AlertConfigurationFactory.createLowReservoirAlertConfiguration(OmnipodConst.LOW_RESERVOIR_ALERT);
DateTime endOfServiceTime = podState.getActivatedAt().plus(OmnipodConst.SERVICE_DURATION);
Duration timeUntilExpirationAdvisoryAlarm = new Duration(DateTime.now(),
endOfServiceTime.minus(OmnipodConst.EXPIRATION_ADVISORY_WINDOW));
Duration timeUntilShutdownImminentAlarm = new Duration(DateTime.now(),
endOfServiceTime.minus(OmnipodConst.END_OF_SERVICE_IMMINENT_WINDOW));
AlertConfiguration expirationAdvisoryAlertConfiguration = AlertConfigurationFactory.createExpirationAdvisoryAlertConfiguration(
timeUntilExpirationAdvisoryAlarm, OmnipodConst.EXPIRATION_ADVISORY_WINDOW);
AlertConfiguration shutdownImminentAlertConfiguration = AlertConfigurationFactory.createShutdownImminentAlertConfiguration(
timeUntilShutdownImminentAlarm);
AlertConfiguration autoOffAlertConfiguration = AlertConfigurationFactory.createAutoOffAlertConfiguration(
false, Duration.ZERO);
List<AlertConfiguration> alertConfigurations = Arrays.asList( //
lowReservoirAlertConfiguration, //
expirationAdvisoryAlertConfiguration, //
shutdownImminentAlertConfiguration, //
autoOffAlertConfiguration //
);
return new ConfigureAlertsAction(podState, alertConfigurations).execute(communicationService);
}
public StatusResponse executeInsertionBolusCommand(OmnipodCommunicationManager communicationService, PodSessionState podState) {
return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_CANNULA_INSERTION_BOLUS_UNITS,
Duration.standardSeconds(1), false, false));
}
}

View file

@ -0,0 +1,37 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
import org.joda.time.Duration;
import java.util.Collections;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationManager;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.FaultConfigCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class PrimeService {
public StatusResponse executeDisableTab5Sub16FaultConfigCommand(OmnipodCommunicationManager communicationService, PodSessionState podState) {
FaultConfigCommand faultConfigCommand = new FaultConfigCommand(podState.getCurrentNonce(), (byte) 0x00, (byte) 0x00);
OmnipodMessage faultConfigMessage = new OmnipodMessage(podState.getAddress(),
Collections.singletonList(faultConfigCommand), podState.getMessageNumber());
return communicationService.exchangeMessages(StatusResponse.class, podState, faultConfigMessage);
}
public StatusResponse executeFinishSetupReminderAlertCommand(OmnipodCommunicationManager communicationService, PodSessionState podState) {
AlertConfiguration finishSetupReminderAlertConfiguration = AlertConfigurationFactory.createFinishSetupReminderAlertConfiguration();
return communicationService.executeAction(new ConfigureAlertsAction(podState,
Collections.singletonList(finishSetupReminderAlertConfiguration)));
}
public StatusResponse executePrimeBolusCommand(OmnipodCommunicationManager communicationService, PodSessionState podState) {
return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_PRIME_BOLUS_UNITS,
Duration.standardSeconds(1), false, false));
}
}

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class ActionInitializationException extends OmnipodException {
public ActionInitializationException(String message) {
super(message, true);
}
}

View file

@ -0,0 +1,13 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class CommandInitializationException extends OmnipodException {
public CommandInitializationException(String message) {
super(message, true);
}
public CommandInitializationException(String message, Throwable cause) {
super(message, cause, true);
}
}

View file

@ -0,0 +1,36 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class CommunicationException extends OmnipodException {
private final Type type;
public CommunicationException(Type type) {
super(type.getDescription(), false);
this.type = type;
}
public CommunicationException(Type type, Throwable cause) {
super(type.getDescription() + ": "+ cause, cause, false);
this.type = type;
}
public Type getType() {
return type;
}
public enum Type {
TIMEOUT("Communication timeout"),
UNEXPECTED_EXCEPTION("Caught an unexpected Exception");
private final String description;
Type(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
}

View file

@ -0,0 +1,24 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class CrcMismatchException extends OmnipodException {
private final int expected;
private final int actual;
public CrcMismatchException(int expected, int actual) {
super(String.format(Locale.getDefault(), "CRC mismatch: expected %d, got %d", expected, actual), false);
this.expected = expected;
this.actual = actual;
}
public int getExpected() {
return expected;
}
public int getActual() {
return actual;
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class IllegalDeliveryStatusException extends OmnipodException {
private final DeliveryStatus expected;
private final DeliveryStatus actual;
public IllegalDeliveryStatusException(DeliveryStatus expected, DeliveryStatus actual) {
super(String.format(Locale.getDefault(), "Illegal delivery status: %s, expected: %s", actual, expected), true);
this.expected = expected;
this.actual = actual;
}
public DeliveryStatus getExpected() {
return expected;
}
public DeliveryStatus getActual() {
return actual;
}
}

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class IllegalPacketTypeException extends OmnipodException {
private final PacketType expected;
private final PacketType actual;
public IllegalPacketTypeException(PacketType expected, PacketType actual) {
super(String.format(Locale.getDefault(), "Illegal packet type: %s, expected %s",
actual, expected), false);
this.expected = expected;
this.actual = actual;
}
public PacketType getExpected() {
return expected;
}
public PacketType getActual() {
return actual;
}
}

View file

@ -0,0 +1,25 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class IllegalPodProgressException extends OmnipodException {
private final PodProgressStatus expected;
private final PodProgressStatus actual;
public IllegalPodProgressException(PodProgressStatus expected, PodProgressStatus actual) {
super(String.format(Locale.getDefault(), "Illegal setup state: %s, expected: %s", actual, expected), true);
this.expected = expected;
this.actual = actual;
}
public PodProgressStatus getExpected() {
return expected;
}
public PodProgressStatus getActual() {
return actual;
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class IllegalResponseException extends OmnipodException {
private final String actualClass;
private final MessageBlockType expectedType;
public IllegalResponseException(String actualClass, MessageBlockType expectedType) {
super(String.format(Locale.getDefault(), "Illegal response type: got class of type %s " +
"for message block type %s", actualClass, expectedType), false);
this.actualClass = actualClass;
this.expectedType = expectedType;
}
public String getActualClass() {
return actualClass;
}
public MessageBlockType getExpectedType() {
return expectedType;
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import java.util.Locale;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class IllegalSetupProgressException extends OmnipodException {
private final SetupProgress expected;
private final SetupProgress actual;
public IllegalSetupProgressException(SetupProgress expected, SetupProgress actual) {
super(String.format(Locale.getDefault(), "Illegal setup progress: %s, expected: %s", actual, expected), true);
this.expected = expected;
this.actual = actual;
}
public SetupProgress getExpected() {
return expected;
}
public SetupProgress getActual() {
return actual;
}
}

View file

@ -0,0 +1,13 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class MessageDecodingException extends OmnipodException {
public MessageDecodingException(String message) {
super(message, false);
}
public MessageDecodingException(String message, Throwable cause) {
super(message, cause, false);
}
}

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class NonceOutOfSyncException extends OmnipodException {
public NonceOutOfSyncException() {
super("Nonce out of sync", true);
}
}

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class NonceResyncException extends OmnipodException {
public NonceResyncException() {
super("Nonce resync failed", true);
}
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class NotEnoughDataException extends OmnipodException {
private final byte[] data;
public NotEnoughDataException(byte[] data) {
super("Not enough data: " + ByteUtil.shortHexString(data), false);
this.data = data;
}
public byte[] getData() {
return data;
}
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class PodFaultException extends OmnipodException {
private final PodInfoFaultEvent faultEvent;
public PodFaultException(PodInfoFaultEvent faultEvent) {
super(faultEvent.getFaultEventType().toString(), true);
this.faultEvent = faultEvent;
}
public PodInfoFaultEvent getFaultEvent() {
return faultEvent;
}
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.exception;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
public class PodReturnedErrorResponseException extends OmnipodException {
private final ErrorResponse errorResponse;
public PodReturnedErrorResponseException(ErrorResponse errorResponse) {
super("Pod returned error response: " + errorResponse.getErrorResponseType(), true);
this.errorResponse = errorResponse;
}
public ErrorResponse getErrorResponse() {
return errorResponse;
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
public interface IRawRepresentable {
byte[] getRawData();
}

View file

@ -0,0 +1,31 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public abstract class MessageBlock {
protected byte[] encodedData = new byte[0];
public MessageBlock() {
}
public abstract MessageBlockType getType();
//This method returns raw message representation
//It should be rewritten in a derived class if raw representation of a concrete message
//is something else than just message type concatenated with message data
public byte[] getRawData() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
stream.write(this.getType().getValue());
stream.write((byte) encodedData.length);
stream.write(encodedData);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return stream.toByteArray();
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
public abstract class NonceResyncableMessageBlock extends MessageBlock {
public abstract int getNonce();
public abstract void setNonce(int nonce);
}

View file

@ -0,0 +1,148 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CrcMismatchException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.MessageDecodingException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.NotEnoughDataException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC;
public class OmnipodMessage {
private final int address;
private final List<MessageBlock> messageBlocks;
private final int sequenceNumber;
public OmnipodMessage(OmnipodMessage other) {
address = other.address;
messageBlocks = new ArrayList<>(other.messageBlocks);
sequenceNumber = other.sequenceNumber;
}
public OmnipodMessage(int address, List<MessageBlock> messageBlocks, int sequenceNumber) {
this.address = address;
this.messageBlocks = messageBlocks;
this.sequenceNumber = sequenceNumber;
}
public static OmnipodMessage decodeMessage(byte[] data) {
if (data.length < 10) {
throw new NotEnoughDataException(data);
}
int address = ByteUtil.toInt((int) data[0], (int) data[1], (int) data[2],
(int) data[3], ByteUtil.BitConversion.BIG_ENDIAN);
byte b9 = data[4];
int bodyLength = ByteUtil.convertUnsignedByteToInt(data[5]);
if (data.length - 8 < bodyLength) {
throw new NotEnoughDataException(data);
}
int sequenceNumber = (((int) b9 >> 2) & 0b11111);
int crc = ByteUtil.toInt(data[data.length - 2], data[data.length - 1]);
int calculatedCrc = OmniCRC.crc16(ByteUtil.substring(data, 0, data.length - 2));
if (crc != calculatedCrc) {
throw new CrcMismatchException(calculatedCrc, crc);
}
List<MessageBlock> blocks = decodeBlocks(ByteUtil.substring(data, 6, data.length - 6 - 2));
if (blocks == null || blocks.size() == 0) {
throw new MessageDecodingException("No blocks decoded");
}
return new OmnipodMessage(address, blocks, sequenceNumber);
}
private static List<MessageBlock> decodeBlocks(byte[] data) {
List<MessageBlock> blocks = new ArrayList<>();
int index = 0;
while (index < data.length) {
try {
MessageBlockType blockType = MessageBlockType.fromByte(data[index]);
MessageBlock block = blockType.decode(ByteUtil.substring(data, index));
blocks.add(block);
int blockLength = block.getRawData().length;
index += blockLength;
} catch (Exception ex) {
throw new MessageDecodingException("Failed to decode blocks", ex);
}
}
return blocks;
}
public byte[] getEncoded() {
byte[] encodedData = new byte[0];
for (MessageBlock messageBlock : messageBlocks) {
encodedData = ByteUtil.concat(encodedData, messageBlock.getRawData());
}
byte[] header = new byte[0];
//right before the message blocks we have 6 bits of seqNum and 10 bits of length
header = ByteUtil.concat(header, ByteUtil.getBytesFromInt(address));
header = ByteUtil.concat(header, (byte) (((sequenceNumber & 0x1F) << 2) + ((encodedData.length >> 8) & 0x03)));
header = ByteUtil.concat(header, (byte) (encodedData.length & 0xFF));
encodedData = ByteUtil.concat(header, encodedData);
int crc = OmniCRC.crc16(encodedData);
encodedData = ByteUtil.concat(encodedData, ByteUtil.substring(ByteUtil.getBytesFromInt(crc), 2, 2));
return encodedData;
}
public void padWithGetStatusCommands(int packetSize) {
while (getEncoded().length < packetSize) {
messageBlocks.add(new GetStatusCommand(PodInfoType.NORMAL));
}
}
public List<MessageBlock> getMessageBlocks() {
return messageBlocks;
}
public int getSequenceNumber() {
return sequenceNumber;
}
public boolean isNonceResyncable() {
return containsBlock(NonceResyncableMessageBlock.class);
}
public int getSentNonce() {
for (MessageBlock messageBlock : messageBlocks) {
if (messageBlock instanceof NonceResyncableMessageBlock) {
return ((NonceResyncableMessageBlock) messageBlock).getNonce();
}
}
throw new UnsupportedOperationException("Message is not nonce resyncable");
}
public void resyncNonce(int nonce) {
for (MessageBlock messageBlock : messageBlocks) {
if (messageBlock instanceof NonceResyncableMessageBlock) {
((NonceResyncableMessageBlock) messageBlock).setNonce(nonce);
}
}
}
public boolean containsBlock(Class<? extends MessageBlock> blockType) {
for (MessageBlock messageBlock : messageBlocks) {
if (blockType.isInstance(messageBlock)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return "OmnipodMessage{" +
"address=" + address +
", messageBlocks=" + messageBlocks +
", encoded=" + ByteUtil.shortHexStringWithoutSpaces(getEncoded()) +
", sequenceNumber=" + sequenceNumber +
'}';
}
}

View file

@ -0,0 +1,82 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
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.omnipod.defs.PacketType;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CrcMismatchException;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.IllegalPacketTypeException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC;
/**
* Created by andy on 6/1/18.
*/
public class OmnipodPacket implements RLMessage {
private int packetAddress = 0;
private PacketType packetType = PacketType.INVALID;
private int sequenceNumber = 0;
private byte[] encodedMessage = null;
private boolean valid = false;
public OmnipodPacket(byte[] encoded) {
if (encoded.length < 7) {
return;
}
this.packetAddress = ByteUtil.toInt((int) encoded[0], (int) encoded[1],
(int) encoded[2], (int) encoded[3], ByteUtil.BitConversion.BIG_ENDIAN);
try {
this.packetType = PacketType.fromByte((byte) (((int) encoded[4] & 0xFF) >> 5));
} catch (IllegalArgumentException ex) {
throw new IllegalPacketTypeException(null, null);
}
this.sequenceNumber = (encoded[4] & 0b11111);
byte crc = OmniCRC.crc8(ByteUtil.substring(encoded, 0, encoded.length - 1));
if (crc != encoded[encoded.length - 1]) {
throw new CrcMismatchException(crc, encoded[encoded.length - 1]);
}
this.encodedMessage = ByteUtil.substring(encoded, 5, encoded.length - 1 - 5);
valid = true;
}
public OmnipodPacket(int packetAddress, PacketType packetType, int packetNumber, byte[] encodedMessage) {
this.packetAddress = packetAddress;
this.packetType = packetType;
this.sequenceNumber = packetNumber;
this.encodedMessage = encodedMessage;
if (encodedMessage.length > packetType.getMaxBodyLength()) {
this.encodedMessage = ByteUtil.substring(encodedMessage, 0, packetType.getMaxBodyLength());
}
this.valid = true;
}
public PacketType getPacketType() {
return packetType;
}
public int getAddress() {
return packetAddress;
}
public int getSequenceNumber() {
return sequenceNumber;
}
public byte[] getEncodedMessage() {
return encodedMessage;
}
@Override
public byte[] getTxData() {
byte[] output = new byte[0];
output = ByteUtil.concat(output, ByteUtil.getBytesFromInt(this.packetAddress));
output = ByteUtil.concat(output, (byte) ((this.packetType.getValue() << 5) + (sequenceNumber & 0b11111)));
output = ByteUtil.concat(output, encodedMessage);
output = ByteUtil.concat(output, OmniCRC.crc8(output));
return output;
}
@Override
public boolean isValid() {
return valid;
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import java.util.Collections;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class AcknowledgeAlertsCommand extends NonceResyncableMessageBlock {
private final AlertSet alerts;
private int nonce;
public AcknowledgeAlertsCommand(int nonce, AlertSet alerts) {
this.nonce = nonce;
this.alerts = alerts;
encode();
}
public AcknowledgeAlertsCommand(int nonce, AlertSlot alertSlot) {
this(nonce, new AlertSet(Collections.singletonList(alertSlot)));
}
@Override
public MessageBlockType getType() {
return MessageBlockType.ACKNOWLEDGE_ALERT;
}
private void encode() {
encodedData = ByteUtil.getBytesFromInt(nonce);
encodedData = ByteUtil.concat(encodedData, alerts.getRawValue());
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "AcknowledgeAlertsCommand{" +
"alerts=" + alerts +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,31 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class AssignAddressCommand extends MessageBlock {
private final int address;
public AssignAddressCommand(int address) {
this.address = address;
encodedData = ByteBuffer.allocate(4).putInt(this.address).array();
}
public int getAddress() {
return address;
}
@Override
public MessageBlockType getType() {
return MessageBlockType.ASSIGN_ADDRESS;
}
@Override
public String toString() {
return "AssignAddressCommand{" +
"address=" + address +
'}';
}
}

View file

@ -0,0 +1,127 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.Duration;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class BasalScheduleExtraCommand extends MessageBlock {
private final boolean acknowledgementBeep;
private final boolean completionBeep;
private final Duration programReminderInterval;
private final byte currentEntryIndex;
private final double remainingPulses;
// We use a double for the delay between pulses because the Joda time API lacks precision for our calculations
private final double delayUntilNextTenthOfPulseInSeconds;
private final List<RateEntry> rateEntries;
public BasalScheduleExtraCommand(boolean acknowledgementBeep, boolean completionBeep,
Duration programReminderInterval, byte currentEntryIndex,
double remainingPulses, double delayUntilNextTenthOfPulseInSeconds, List<RateEntry> rateEntries) {
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
this.programReminderInterval = programReminderInterval;
this.currentEntryIndex = currentEntryIndex;
this.remainingPulses = remainingPulses;
this.delayUntilNextTenthOfPulseInSeconds = delayUntilNextTenthOfPulseInSeconds;
this.rateEntries = rateEntries;
encode();
}
public BasalScheduleExtraCommand(BasalSchedule schedule, Duration scheduleOffset,
boolean acknowledgementBeep, boolean completionBeep, Duration programReminderInterval) {
rateEntries = new ArrayList<>();
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
this.programReminderInterval = programReminderInterval;
Duration scheduleOffsetNearestSecond = Duration.standardSeconds(Math.round(scheduleOffset.getMillis() / 1000.0));
BasalSchedule mergedSchedule = new BasalSchedule(schedule.adjacentEqualRatesMergedEntries());
List<BasalSchedule.BasalScheduleDurationEntry> durations = mergedSchedule.getDurations();
for (BasalSchedule.BasalScheduleDurationEntry entry : durations) {
rateEntries.addAll(RateEntry.createEntries(entry.getRate(), entry.getDuration()));
}
BasalSchedule.BasalScheduleLookupResult entryLookupResult = mergedSchedule.lookup(scheduleOffsetNearestSecond);
currentEntryIndex = (byte) entryLookupResult.getIndex();
double timeRemainingInEntryInSeconds = entryLookupResult.getStartTime().minus(scheduleOffsetNearestSecond.minus(entryLookupResult.getDuration())).getMillis() / 1000.0;
double rate = mergedSchedule.rateAt(scheduleOffsetNearestSecond);
int pulsesPerHour = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE);
double timeBetweenPulses = 3600.0 / pulsesPerHour;
delayUntilNextTenthOfPulseInSeconds = (timeRemainingInEntryInSeconds % (timeBetweenPulses / 10.0));
remainingPulses = pulsesPerHour * (timeRemainingInEntryInSeconds - delayUntilNextTenthOfPulseInSeconds) / 3600.0 + 0.1;
encode();
}
private void encode() {
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
encodedData = new byte[]{
beepOptions,
currentEntryIndex
};
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10)));
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) Math.round(delayUntilNextTenthOfPulseInSeconds * 1000 * 1000)));
for (RateEntry entry : rateEntries) {
encodedData = ByteUtil.concat(encodedData, entry.getRawData());
}
}
@Override
public MessageBlockType getType() {
return MessageBlockType.BASAL_SCHEDULE_EXTRA;
}
public boolean isAcknowledgementBeep() {
return acknowledgementBeep;
}
public boolean isCompletionBeep() {
return completionBeep;
}
public Duration getProgramReminderInterval() {
return programReminderInterval;
}
public byte getCurrentEntryIndex() {
return currentEntryIndex;
}
public double getRemainingPulses() {
return remainingPulses;
}
public double getDelayUntilNextTenthOfPulseInSeconds() {
return delayUntilNextTenthOfPulseInSeconds;
}
public List<RateEntry> getRateEntries() {
return new ArrayList<>(rateEntries);
}
@Override
public String toString() {
return "BasalScheduleExtraCommand{" +
"acknowledgementBeep=" + acknowledgementBeep +
", completionBeep=" + completionBeep +
", programReminderInterval=" + programReminderInterval +
", currentEntryIndex=" + currentEntryIndex +
", remainingPulses=" + remainingPulses +
", delayUntilNextTenthOfPulseInSeconds=" + delayUntilNextTenthOfPulseInSeconds +
", rateEntries=" + rateEntries +
'}';
}
}

View file

@ -0,0 +1,61 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepConfigType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class BeepConfigCommand extends MessageBlock {
private final BeepConfigType beepType;
private final boolean basalCompletionBeep;
private final Duration basalIntervalBeep;
private final boolean tempBasalCompletionBeep;
private final Duration tempBasalIntervalBeep;
private final boolean bolusCompletionBeep;
private final Duration bolusIntervalBeep;
public BeepConfigCommand(BeepConfigType beepType, boolean basalCompletionBeep, Duration basalIntervalBeep,
boolean tempBasalCompletionBeep, Duration tempBasalIntervalBeep,
boolean bolusCompletionBeep, Duration bolusIntervalBeep) {
this.beepType = beepType;
this.basalCompletionBeep = basalCompletionBeep;
this.basalIntervalBeep = basalIntervalBeep;
this.tempBasalCompletionBeep = tempBasalCompletionBeep;
this.tempBasalIntervalBeep = tempBasalIntervalBeep;
this.bolusCompletionBeep = bolusCompletionBeep;
this.bolusIntervalBeep = bolusIntervalBeep;
encode();
}
public BeepConfigCommand(BeepConfigType beepType) {
this(beepType, false, Duration.ZERO, false, Duration.ZERO, false, Duration.ZERO);
}
private void encode() {
encodedData = new byte[]{beepType.getValue()};
encodedData = ByteUtil.concat(encodedData, (byte) ((basalCompletionBeep ? (1 << 6) : 0) + (basalIntervalBeep.getStandardMinutes() & 0x3f)));
encodedData = ByteUtil.concat(encodedData, (byte) ((tempBasalCompletionBeep ? (1 << 6) : 0) + (tempBasalIntervalBeep.getStandardMinutes() & 0x3f)));
encodedData = ByteUtil.concat(encodedData, (byte) ((bolusCompletionBeep ? (1 << 6) : 0) + (bolusIntervalBeep.getStandardMinutes() & 0x3f)));
}
@Override
public MessageBlockType getType() {
return MessageBlockType.BEEP_CONFIG;
}
@Override
public String toString() {
return "BeepConfigCommand{" +
"beepType=" + beepType +
", basalCompletionBeep=" + basalCompletionBeep +
", basalIntervalBeep=" + basalIntervalBeep +
", tempBasalCompletionBeep=" + tempBasalCompletionBeep +
", tempBasalIntervalBeep=" + tempBasalIntervalBeep +
", bolusCompletionBeep=" + bolusCompletionBeep +
", bolusIntervalBeep=" + bolusIntervalBeep +
'}';
}
}

View file

@ -0,0 +1,76 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CommandInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class BolusExtraCommand extends MessageBlock {
private final boolean acknowledgementBeep;
private final boolean completionBeep;
private final Duration programReminderInterval;
private final double units;
private final Duration timeBetweenPulses;
private final double squareWaveUnits;
private final Duration squareWaveDuration;
public BolusExtraCommand(double units, boolean acknowledgementBeep, boolean completionBeep) {
this(units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep);
}
public BolusExtraCommand(double units, Duration timeBetweenPulses, boolean acknowledgementBeep, boolean completionBeep) {
this(units, 0.0, Duration.ZERO, acknowledgementBeep, completionBeep, Duration.ZERO, timeBetweenPulses);
}
public BolusExtraCommand(double units, double squareWaveUnits, Duration squareWaveDuration,
boolean acknowledgementBeep, boolean completionBeep,
Duration programReminderInterval, Duration timeBetweenPulses) {
if (units <= 0D) {
throw new CommandInitializationException("Units should be > 0");
} else if (units > OmnipodConst.MAX_BOLUS) {
throw new CommandInitializationException("Units exceeds max bolus");
}
this.units = units;
this.squareWaveUnits = squareWaveUnits;
this.squareWaveDuration = squareWaveDuration;
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
this.programReminderInterval = programReminderInterval;
this.timeBetweenPulses = timeBetweenPulses;
encode();
}
private void encode() {
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
int squareWavePulseCountCountX10 = (int) Math.round(squareWaveUnits * 200);
int timeBetweenExtendedPulses = squareWavePulseCountCountX10 > 0 ? (int) squareWaveDuration.getMillis() * 100 / squareWavePulseCountCountX10 : 0;
encodedData = ByteUtil.concat(encodedData, beepOptions);
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(units * 200)));
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) timeBetweenPulses.getMillis() * 100));
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(squareWavePulseCountCountX10));
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(timeBetweenExtendedPulses));
}
@Override
public MessageBlockType getType() {
return MessageBlockType.BOLUS_EXTRA;
}
@Override
public String toString() {
return "BolusExtraCommand{" +
"acknowledgementBeep=" + acknowledgementBeep +
", completionBeep=" + completionBeep +
", programReminderInterval=" + programReminderInterval +
", units=" + units +
", timeBetweenPulses=" + timeBetweenPulses +
", squareWaveUnits=" + squareWaveUnits +
", squareWaveDuration=" + squareWaveDuration +
'}';
}
}

View file

@ -0,0 +1,71 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import java.util.EnumSet;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class CancelDeliveryCommand extends NonceResyncableMessageBlock {
private final BeepType beepType;
private final EnumSet<DeliveryType> deliveryTypes;
private int nonce;
public CancelDeliveryCommand(int nonce, BeepType beepType, EnumSet<DeliveryType> deliveryTypes) {
this.nonce = nonce;
this.beepType = beepType;
this.deliveryTypes = deliveryTypes;
encode();
}
public CancelDeliveryCommand(int nonce, BeepType beepType, DeliveryType deliveryType) {
this(nonce, beepType, EnumSet.of(deliveryType));
}
@Override
public MessageBlockType getType() {
return MessageBlockType.CANCEL_DELIVERY;
}
private void encode() {
encodedData = new byte[5];
System.arraycopy(ByteUtil.getBytesFromInt(nonce), 0, encodedData, 0, 4);
byte beepTypeValue = beepType.getValue();
if (beepTypeValue > 8) {
beepTypeValue = 0;
}
encodedData[4] = (byte) ((beepTypeValue & 0x0F) << 4);
if (deliveryTypes.contains(DeliveryType.BASAL)) {
encodedData[4] |= 1;
}
if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) {
encodedData[4] |= 2;
}
if (deliveryTypes.contains(DeliveryType.BOLUS)) {
encodedData[4] |= 4;
}
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "CancelDeliveryCommand{" +
"beepType=" + beepType +
", deliveryTypes=" + deliveryTypes +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class ConfigureAlertsCommand extends NonceResyncableMessageBlock {
private final List<AlertConfiguration> configurations;
private int nonce;
public ConfigureAlertsCommand(int nonce, List<AlertConfiguration> configurations) {
this.nonce = nonce;
this.configurations = configurations;
encode();
}
@Override
public MessageBlockType getType() {
return MessageBlockType.CONFIGURE_ALERTS;
}
private void encode() {
encodedData = ByteUtil.getBytesFromInt(nonce);
for (AlertConfiguration config : configurations) {
encodedData = ByteUtil.concat(encodedData, config.getRawData());
}
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "ConfigureAlertsCommand{" +
"configurations=" + configurations +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,56 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.DateTime;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class ConfigurePodCommand extends MessageBlock {
private static final byte PACKET_TIMEOUT_LIMIT = 0x04;
private final int lot;
private final int tid;
private final DateTime date;
private final int address;
public ConfigurePodCommand(int address, DateTime date, int lot, int tid) {
this.address = address;
this.lot = lot;
this.tid = tid;
this.date = date;
encode();
}
@Override
public MessageBlockType getType() {
return MessageBlockType.SETUP_POD;
}
private void encode() {
encodedData = new byte[0];
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(address));
encodedData = ByteUtil.concat(encodedData, new byte[]{ //
(byte) 0x14, // unknown
PACKET_TIMEOUT_LIMIT, //
(byte) date.monthOfYear().get(), //
(byte) date.dayOfMonth().get(), //
(byte) (date.year().get() - 2000), //
(byte) date.hourOfDay().get(), //
(byte) date.minuteOfHour().get() //
});
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(lot));
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(tid));
}
@Override
public String toString() {
return "ConfigurePodCommand{" +
"lot=" + lot +
", tid=" + tid +
", date=" + date +
", address=" + address +
'}';
}
}

View file

@ -0,0 +1,41 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class DeactivatePodCommand extends NonceResyncableMessageBlock {
private int nonce;
public DeactivatePodCommand(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public MessageBlockType getType() {
return MessageBlockType.DEACTIVATE_POD;
}
private void encode() {
encodedData = ByteUtil.getBytesFromInt(nonce);
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "DeactivatePodCommand{" +
"nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class FaultConfigCommand extends NonceResyncableMessageBlock {
private final byte tab5sub16;
private final byte tab5sub17;
private int nonce;
public FaultConfigCommand(int nonce, byte tab5sub16, byte tab5sub17) {
this.nonce = nonce;
this.tab5sub16 = tab5sub16;
this.tab5sub17 = tab5sub17;
encode();
}
private void encode() {
encodedData = ByteUtil.getBytesFromInt(nonce);
encodedData = ByteUtil.concat(encodedData, tab5sub16);
encodedData = ByteUtil.concat(encodedData, tab5sub17);
}
@Override
public MessageBlockType getType() {
return MessageBlockType.FAULT_CONFIG;
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "FaultConfigCommand{" +
"tab5sub16=" + tab5sub16 +
", tab5sub17=" + tab5sub17 +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,30 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class GetStatusCommand extends MessageBlock {
private final PodInfoType podInfoType;
public GetStatusCommand(PodInfoType podInfoType) {
this.podInfoType = podInfoType;
encode();
}
private void encode() {
encodedData = new byte[]{podInfoType.getValue()};
}
@Override
public MessageBlockType getType() {
return MessageBlockType.GET_STATUS;
}
@Override
public String toString() {
return "GetStatusCommand{" +
"podInfoType=" + podInfoType +
'}';
}
}

View file

@ -0,0 +1,98 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliverySchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliveryTable;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.DeliverySchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.TempBasalDeliverySchedule;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CommandInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class SetInsulinScheduleCommand extends NonceResyncableMessageBlock {
private final DeliverySchedule schedule;
private int nonce;
// Bolus
public SetInsulinScheduleCommand(int nonce, BolusDeliverySchedule schedule) {
this.nonce = nonce;
this.schedule = schedule;
encode();
}
// Basal schedule
public SetInsulinScheduleCommand(int nonce, BasalSchedule schedule, Duration scheduleOffset) {
int scheduleOffsetInSeconds = (int) scheduleOffset.getStandardSeconds();
BasalDeliveryTable table = new BasalDeliveryTable(schedule);
double rate = schedule.rateAt(scheduleOffset);
byte segment = (byte) (scheduleOffsetInSeconds / BasalDeliveryTable.SEGMENT_DURATION);
int segmentOffset = scheduleOffsetInSeconds % BasalDeliveryTable.SEGMENT_DURATION;
int timeRemainingInSegment = BasalDeliveryTable.SEGMENT_DURATION - segmentOffset;
double timeBetweenPulses = 3600 / (rate / OmnipodConst.POD_PULSE_SIZE);
double offsetToNextTenth = timeRemainingInSegment % (timeBetweenPulses / 10.0);
int pulsesRemainingInSegment = (int) ((timeRemainingInSegment + timeBetweenPulses / 10.0 - offsetToNextTenth) / timeBetweenPulses);
this.nonce = nonce;
this.schedule = new BasalDeliverySchedule(segment, timeRemainingInSegment, pulsesRemainingInSegment, table);
encode();
}
// Temp basal
public SetInsulinScheduleCommand(int nonce, double tempBasalRate, Duration duration) {
if (tempBasalRate < 0D) {
throw new CommandInitializationException("Rate should be >= 0");
} else if (tempBasalRate > OmnipodConst.MAX_BASAL_RATE) {
throw new CommandInitializationException("Rate exceeds max basal rate");
}
if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) {
throw new CommandInitializationException("Duration exceeds max temp basal duration");
}
int pulsesPerHour = (int) Math.round(tempBasalRate / OmnipodConst.POD_PULSE_SIZE);
int pulsesPerSegment = pulsesPerHour / 2;
this.nonce = nonce;
this.schedule = new TempBasalDeliverySchedule(BasalDeliveryTable.SEGMENT_DURATION, pulsesPerSegment, new BasalDeliveryTable(tempBasalRate, duration));
encode();
}
private void encode() {
encodedData = ByteUtil.getBytesFromInt(nonce);
encodedData = ByteUtil.concat(encodedData, schedule.getType().getValue());
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(schedule.getChecksum()));
encodedData = ByteUtil.concat(encodedData, schedule.getRawData());
}
@Override
public MessageBlockType getType() {
return MessageBlockType.SET_INSULIN_SCHEDULE;
}
@Override
public int getNonce() {
return nonce;
}
@Override
public void setNonce(int nonce) {
this.nonce = nonce;
encode();
}
@Override
public String toString() {
return "SetInsulinScheduleCommand{" +
"schedule=" + schedule +
", nonce=" + nonce +
'}';
}
}

View file

@ -0,0 +1,108 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
import org.joda.time.Duration;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.exception.CommandInitializationException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class TempBasalExtraCommand extends MessageBlock {
private final boolean acknowledgementBeep;
private final boolean completionBeep;
private final Duration programReminderInterval;
private final double remainingPulses;
// We use a double for the delay until next pulse because the Joda time API lacks precision for our calculations
private final double delayUntilNextPulse;
private final List<RateEntry> rateEntries;
public TempBasalExtraCommand(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep,
Duration programReminderInterval) {
if (rate < 0D) {
throw new CommandInitializationException("Rate should be >= 0");
} else if (rate > OmnipodConst.MAX_BASAL_RATE) {
throw new CommandInitializationException("Rate exceeds max basal rate");
}
if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) {
throw new CommandInitializationException("Duration exceeds max temp basal duration");
}
this.acknowledgementBeep = acknowledgementBeep;
this.completionBeep = completionBeep;
this.programReminderInterval = programReminderInterval;
rateEntries = RateEntry.createEntries(rate, duration);
RateEntry currentRateEntry = rateEntries.get(0);
remainingPulses = currentRateEntry.getTotalPulses();
delayUntilNextPulse = currentRateEntry.getDelayBetweenPulsesInSeconds();
encode();
}
private void encode() {
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
encodedData = new byte[]{
beepOptions,
(byte) 0x00
};
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10)));
if (remainingPulses == 0) {
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100) * 10));
} else {
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100)));
}
for (RateEntry entry : rateEntries) {
encodedData = ByteUtil.concat(encodedData, entry.getRawData());
}
}
@Override
public MessageBlockType getType() {
return MessageBlockType.TEMP_BASAL_EXTRA;
}
public boolean isAcknowledgementBeep() {
return acknowledgementBeep;
}
public boolean isCompletionBeep() {
return completionBeep;
}
public Duration getProgramReminderInterval() {
return programReminderInterval;
}
public double getRemainingPulses() {
return remainingPulses;
}
public double getDelayUntilNextPulse() {
return delayUntilNextPulse;
}
public List<RateEntry> getRateEntries() {
return new ArrayList<>(rateEntries);
}
@Override
public String toString() {
return "TempBasalExtraCommand{" +
"acknowledgementBeep=" + acknowledgementBeep +
", completionBeep=" + completionBeep +
", programReminderInterval=" + programReminderInterval +
", remainingPulses=" + remainingPulses +
", delayUntilNextPulse=" + delayUntilNextPulse +
", rateEntries=" + rateEntries +
'}';
}
}

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
public class ErrorResponse extends MessageBlock {
private static final int MESSAGE_LENGTH = 5;
private final ErrorResponseType errorResponseType;
private final int nonceSearchKey;
public ErrorResponse(byte[] encodedData) {
if (encodedData.length < MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
this.encodedData = ByteUtil.substring(encodedData, 2, MESSAGE_LENGTH - 2);
ErrorResponseType errorResponseType = null;
try {
errorResponseType = ErrorResponseType.fromByte(encodedData[2]);
} catch (IllegalArgumentException ex) {
}
this.errorResponseType = errorResponseType;
this.nonceSearchKey = ByteUtil.makeUnsignedShort((int) encodedData[3], (int) encodedData[4]);
}
@Override
public MessageBlockType getType() {
return MessageBlockType.ERROR_RESPONSE;
}
public ErrorResponseType getErrorResponseType() {
return errorResponseType;
}
public int getNonceSearchKey() {
return nonceSearchKey;
}
@Override
public String toString() {
return "ErrorResponse{" +
"errorResponseType=" + errorResponseType +
", nonceSearchKey=" + nonceSearchKey +
'}';
}
}

View file

@ -0,0 +1,119 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
import org.joda.time.Duration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class StatusResponse extends MessageBlock {
private static final int MESSAGE_LENGTH = 10;
private final DeliveryStatus deliveryStatus;
private final PodProgressStatus podProgressStatus;
private final Duration timeActive;
private final Double reservoirLevel;
private final double insulinDelivered;
private final double insulinNotDelivered;
private final byte podMessageCounter;
private final AlertSet alerts;
public StatusResponse(byte[] encodedData) {
if (encodedData.length < MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
this.encodedData = ByteUtil.substring(encodedData, 1, MESSAGE_LENGTH - 1);
this.deliveryStatus = DeliveryStatus.fromByte((byte) (ByteUtil.convertUnsignedByteToInt(encodedData[1]) >>> 4));
this.podProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[1] & 0x0F));
int minutes = ((encodedData[7] & 0x7F) << 6) | ((encodedData[8] & 0xFC) >>> 2);
this.timeActive = Duration.standardMinutes(minutes);
int highInsulinBits = (encodedData[2] & 0xF) << 9;
int middleInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[3]) << 1;
int lowInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[4]) >>> 7;
this.insulinDelivered = OmnipodConst.POD_PULSE_SIZE * (highInsulinBits | middleInsulinBits | lowInsulinBits);
this.podMessageCounter = (byte) ((encodedData[4] >>> 3) & 0xf);
this.insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * (((encodedData[4] & 0x03) << 8) | ByteUtil.convertUnsignedByteToInt(encodedData[5]));
this.alerts = new AlertSet((byte) (((encodedData[6] & 0x7f) << 1) | (ByteUtil.convertUnsignedByteToInt(encodedData[7]) >>> 7)));
double reservoirValue = (((encodedData[8] & 0x3) << 8) + ByteUtil.convertUnsignedByteToInt(encodedData[9])) * OmnipodConst.POD_PULSE_SIZE;
if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) {
reservoirLevel = null;
} else {
reservoirLevel = reservoirValue;
}
}
@Override
public MessageBlockType getType() {
return MessageBlockType.STATUS_RESPONSE;
}
public DeliveryStatus getDeliveryStatus() {
return deliveryStatus;
}
public PodProgressStatus getPodProgressStatus() {
return podProgressStatus;
}
public Duration getTimeActive() {
return timeActive;
}
public Double getReservoirLevel() {
return reservoirLevel;
}
public double getInsulinDelivered() {
return insulinDelivered;
}
public double getInsulinNotDelivered() {
return insulinNotDelivered;
}
public byte getPodMessageCounter() {
return podMessageCounter;
}
public AlertSet getAlerts() {
return alerts;
}
public byte[] getRawData() {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
stream.write(this.getType().getValue());
stream.write(encodedData);
} catch (IOException e) {
e.printStackTrace();
return null;
}
return stream.toByteArray();
}
@Override
public String toString() {
return "StatusResponse{" +
"deliveryStatus=" + deliveryStatus +
", podProgressStatus=" + podProgressStatus +
", timeActive=" + timeActive +
", reservoirLevel=" + reservoirLevel +
", insulinDelivered=" + insulinDelivered +
", insulinNotDelivered=" + insulinNotDelivered +
", podMessageCounter=" + podMessageCounter +
", alerts=" + alerts +
'}';
}
}

View file

@ -0,0 +1,91 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
public class VersionResponse extends MessageBlock {
private final PodProgressStatus podProgressStatus;
private final FirmwareVersion pmVersion;
private final FirmwareVersion piVersion;
private final int lot;
private final int tid;
private final int address;
public VersionResponse(byte[] encodedData) {
int length = ByteUtil.convertUnsignedByteToInt(encodedData[1]) + 2;
this.encodedData = ByteUtil.substring(encodedData, 2, length - 2);
boolean extraByte;
byte[] truncatedData;
switch (length) {
case 0x17:
truncatedData = ByteUtil.substring(encodedData, 2);
extraByte = true;
break;
case 0x1D:
truncatedData = ByteUtil.substring(encodedData, 9);
extraByte = false;
break;
default:
throw new IllegalArgumentException("Unrecognized VersionResponse message length: " + length);
}
this.podProgressStatus = PodProgressStatus.fromByte(truncatedData[7]);
this.pmVersion = new FirmwareVersion(truncatedData[0], truncatedData[1], truncatedData[2]);
this.piVersion = new FirmwareVersion(truncatedData[3], truncatedData[4], truncatedData[5]);
this.lot = ByteUtil.toInt((int) truncatedData[8], (int) truncatedData[9],
(int) truncatedData[10], (int) truncatedData[11], ByteUtil.BitConversion.BIG_ENDIAN);
this.tid = ByteUtil.toInt((int) truncatedData[12], (int) truncatedData[13],
(int) truncatedData[14], (int) truncatedData[15], ByteUtil.BitConversion.BIG_ENDIAN);
int indexIncrementor = extraByte ? 1 : 0;
this.address = ByteUtil.toInt((int) truncatedData[16 + indexIncrementor], (int) truncatedData[17 + indexIncrementor],
(int) truncatedData[18 + indexIncrementor], (int) truncatedData[19 + indexIncrementor], ByteUtil.BitConversion.BIG_ENDIAN);
}
@Override
public MessageBlockType getType() {
return MessageBlockType.VERSION_RESPONSE;
}
public PodProgressStatus getPodProgressStatus() {
return podProgressStatus;
}
public FirmwareVersion getPmVersion() {
return pmVersion;
}
public FirmwareVersion getPiVersion() {
return piVersion;
}
public int getLot() {
return lot;
}
public int getTid() {
return tid;
}
public int getAddress() {
return address;
}
@Override
public String toString() {
return "VersionResponse{" +
"podProgressStatus=" + podProgressStatus +
", pmVersion=" + pmVersion +
", piVersion=" + piVersion +
", lot=" + lot +
", tid=" + tid +
", address=" + address +
'}';
}
}

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public abstract class PodInfo {
private final byte[] encodedData;
public PodInfo(byte[] encodedData) {
this.encodedData = encodedData;
}
public abstract PodInfoType getType();
public byte[] getEncodedData() {
return encodedData;
}
}

View file

@ -0,0 +1,92 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import org.joda.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class PodInfoActiveAlerts extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 11;
private final byte[] word278; // Unknown use
private final List<AlertActivation> alertActivations;
public PodInfoActiveAlerts(byte[] encodedData) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
word278 = ByteUtil.substring(encodedData, 1, 2);
alertActivations = new ArrayList<>();
for (AlertSlot alertSlot : AlertSlot.values()) {
int valueHighBits = ByteUtil.convertUnsignedByteToInt(encodedData[3 + alertSlot.getValue() * 2]);
int valueLowBits = ByteUtil.convertUnsignedByteToInt(encodedData[4 + alertSlot.getValue() * 2]);
int value = (valueHighBits << 8) | valueLowBits;
if (value != 0) {
alertActivations.add(new AlertActivation(alertSlot, value));
}
}
}
@Override
public PodInfoType getType() {
return PodInfoType.ACTIVE_ALERTS;
}
public byte[] getWord278() {
return word278;
}
public List<AlertActivation> getAlertActivations() {
return new ArrayList<>(alertActivations);
}
@Override
public String toString() {
return "PodInfoActiveAlerts{" +
"word278=" + Arrays.toString(word278) +
", alertActivations=" + alertActivations +
'}';
}
public static class AlertActivation {
private final AlertSlot alertSlot;
private final int value;
private AlertActivation(AlertSlot alertSlot, int value) {
this.alertSlot = alertSlot;
this.value = value;
}
public double getValueAsUnits() {
return value * OmnipodConst.POD_PULSE_SIZE;
}
public Duration getValueAsDuration() {
return Duration.standardMinutes(value);
}
public AlertSlot getAlertSlot() {
return alertSlot;
}
@Override
public String toString() {
return "AlertActivation{" +
"alertSlot=" + alertSlot +
", valueAsUnits=" + getValueAsUnits() +
", valueAsDuration=" + getValueAsDuration() +
'}';
}
}
}

View file

@ -0,0 +1,83 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import org.joda.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class PodInfoDataLog extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 8;
private final FaultEventType faultEventType;
private final Duration timeFaultEvent;
private final Duration timeSinceActivation;
private final byte dataChunkSize;
private final byte maximumNumberOfDwords;
private final List<byte[]> dwords;
public PodInfoDataLog(byte[] encodedData, int bodyLength) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
faultEventType = FaultEventType.fromByte(encodedData[1]);
timeFaultEvent = Duration.standardMinutes(ByteUtil.toInt(encodedData[2], encodedData[3]));
timeSinceActivation = Duration.standardMinutes(ByteUtil.toInt(encodedData[4], encodedData[5]));
dataChunkSize = encodedData[6];
maximumNumberOfDwords = encodedData[7];
dwords = new ArrayList<>();
int numberOfDwords = (bodyLength - 8) / 4;
for (int i = 0; i < numberOfDwords; i++) {
dwords.add(ByteUtil.substring(encodedData, 8 + (4 * i), 4));
}
}
@Override
public PodInfoType getType() {
return PodInfoType.DATA_LOG;
}
public FaultEventType getFaultEventType() {
return faultEventType;
}
public Duration getTimeFaultEvent() {
return timeFaultEvent;
}
public Duration getTimeSinceActivation() {
return timeSinceActivation;
}
public byte getDataChunkSize() {
return dataChunkSize;
}
public byte getMaximumNumberOfDwords() {
return maximumNumberOfDwords;
}
public List<byte[]> getDwords() {
return Collections.unmodifiableList(dwords);
}
@Override
public String toString() {
return "PodInfoDataLog{" +
"faultEventType=" + faultEventType +
", timeFaultEvent=" + timeFaultEvent +
", timeSinceActivation=" + timeSinceActivation +
", dataChunkSize=" + dataChunkSize +
", maximumNumberOfDwords=" + maximumNumberOfDwords +
", dwords=" + dwords +
'}';
}
}

View file

@ -0,0 +1,54 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class PodInfoFaultAndInitializationTime extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 17;
private final FaultEventType faultEventType;
private final Duration timeFaultEvent;
private final DateTime initializationTime;
public PodInfoFaultAndInitializationTime(byte[] encodedData) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
faultEventType = FaultEventType.fromByte(encodedData[1]);
timeFaultEvent = Duration.standardMinutes(((encodedData[2] & 0b1) << 8) + encodedData[3]);
// We ignore time zones here because we don't keep the time zone in which the pod was initially set up
// Which is fine because we don't use the initialization time for anything important anyway
initializationTime = new DateTime(2000 + encodedData[14], encodedData[12], encodedData[13], encodedData[15], encodedData[16]);
}
@Override
public PodInfoType getType() {
return PodInfoType.FAULT_AND_INITIALIZATION_TIME;
}
public FaultEventType getFaultEventType() {
return faultEventType;
}
public Duration getTimeFaultEvent() {
return timeFaultEvent;
}
public DateTime getInitializationTime() {
return initializationTime;
}
@Override
public String toString() {
return "PodInfoFaultAndInitializationTime{" +
"faultEventType=" + faultEventType +
", timeFaultEvent=" + timeFaultEvent +
", initializationTime=" + initializationTime +
'}';
}
}

View file

@ -0,0 +1,172 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.LogEventErrorCode;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class PodInfoFaultEvent extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 21;
private final PodProgressStatus podProgressStatus;
private final DeliveryStatus deliveryStatus;
private final double insulinNotDelivered;
private final byte podMessageCounter;
private final double totalInsulinDelivered;
private final FaultEventType faultEventType;
private final Duration faultEventTime;
private final Double reservoirLevel;
private final Duration timeSinceActivation;
private final AlertSet unacknowledgedAlerts;
private final boolean faultAccessingTables;
private final LogEventErrorCode logEventErrorType;
private final PodProgressStatus logEventErrorPodProgressStatus;
private final byte receiverLowGain;
private final byte radioRSSI;
private final PodProgressStatus podProgressStatusAtTimeOfFirstLoggedFaultEvent;
private final byte[] unknownValue;
public PodInfoFaultEvent(byte[] encodedData) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
podProgressStatus = PodProgressStatus.fromByte(encodedData[1]);
deliveryStatus = DeliveryStatus.fromByte(encodedData[2]);
insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[3], encodedData[4]);
podMessageCounter = encodedData[5];
totalInsulinDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[6], encodedData[7]);
faultEventType = FaultEventType.fromByte(encodedData[8]);
int minutesSinceActivation = ByteUtil.toInt(encodedData[9], encodedData[10]);
if (minutesSinceActivation == 0xffff) {
faultEventTime = null;
} else {
faultEventTime = Duration.standardMinutes(minutesSinceActivation);
}
double reservoirValue = ((encodedData[11] & 0x03) << 8) +
ByteUtil.convertUnsignedByteToInt(encodedData[12]) * OmnipodConst.POD_PULSE_SIZE;
if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) {
reservoirLevel = null;
} else {
reservoirLevel = reservoirValue;
}
int minutesActive = ByteUtil.toInt(encodedData[13], encodedData[14]);
timeSinceActivation = Duration.standardMinutes(minutesActive);
unacknowledgedAlerts = new AlertSet(encodedData[15]);
faultAccessingTables = encodedData[16] == 0x02;
logEventErrorType = LogEventErrorCode.fromByte((byte) (encodedData[17] >>> 4));
logEventErrorPodProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[17] & 0x0f));
receiverLowGain = (byte) (ByteUtil.convertUnsignedByteToInt(encodedData[18]) >>> 6);
radioRSSI = (byte) (encodedData[18] & 0x3f);
podProgressStatusAtTimeOfFirstLoggedFaultEvent = PodProgressStatus.fromByte((byte) (encodedData[19] & 0x0f));
unknownValue = ByteUtil.substring(encodedData, 20, 2);
}
@Override
public PodInfoType getType() {
return PodInfoType.FAULT_EVENT;
}
public PodProgressStatus getPodProgressStatus() {
return podProgressStatus;
}
public DeliveryStatus getDeliveryStatus() {
return deliveryStatus;
}
public double getInsulinNotDelivered() {
return insulinNotDelivered;
}
public byte getPodMessageCounter() {
return podMessageCounter;
}
public double getTotalInsulinDelivered() {
return totalInsulinDelivered;
}
public FaultEventType getFaultEventType() {
return faultEventType;
}
public Duration getFaultEventTime() {
return faultEventTime;
}
public Double getReservoirLevel() {
return reservoirLevel;
}
public Duration getTimeSinceActivation() {
return timeSinceActivation;
}
public AlertSet getUnacknowledgedAlerts() {
return unacknowledgedAlerts;
}
public boolean isFaultAccessingTables() {
return faultAccessingTables;
}
public LogEventErrorCode getLogEventErrorType() {
return logEventErrorType;
}
public PodProgressStatus getLogEventErrorPodProgressStatus() {
return logEventErrorPodProgressStatus;
}
public byte getReceiverLowGain() {
return receiverLowGain;
}
public byte getRadioRSSI() {
return radioRSSI;
}
public PodProgressStatus getPodProgressStatusAtTimeOfFirstLoggedFaultEvent() {
return podProgressStatusAtTimeOfFirstLoggedFaultEvent;
}
public byte[] getUnknownValue() {
return unknownValue;
}
@Override
public String toString() {
return "PodInfoFaultEvent{" +
"podProgressStatus=" + podProgressStatus +
", deliveryStatus=" + deliveryStatus +
", insulinNotDelivered=" + insulinNotDelivered +
", podMessageCounter=" + podMessageCounter +
", totalInsulinDelivered=" + totalInsulinDelivered +
", faultEventType=" + faultEventType +
", faultEventTime=" + faultEventTime +
", reservoirLevel=" + reservoirLevel +
", timeSinceActivation=" + timeSinceActivation +
", unacknowledgedAlerts=" + unacknowledgedAlerts +
", faultAccessingTables=" + faultAccessingTables +
", logEventErrorType=" + logEventErrorType +
", logEventErrorPodProgressStatus=" + logEventErrorPodProgressStatus +
", receiverLowGain=" + receiverLowGain +
", radioRSSI=" + radioRSSI +
", podProgressStatusAtTimeOfFirstLoggedFaultEvent=" + podProgressStatusAtTimeOfFirstLoggedFaultEvent +
", unknownValue=" + ByteUtil.shortHexString(unknownValue) +
'}';
}
}

View file

@ -0,0 +1,56 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class PodInfoOlderPulseLog extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 3;
private final ArrayList<byte[]> dwords;
public PodInfoOlderPulseLog(byte[] encodedData) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
dwords = new ArrayList<>();
int numberOfDwordLogEntries = ByteUtil.toInt(encodedData[1], encodedData[2]);
for (int i = 0; numberOfDwordLogEntries > i; i++) {
byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4);
dwords.add(dword);
}
}
@Override
public PodInfoType getType() {
return PodInfoType.OLDER_PULSE_LOG;
}
public List<byte[]> getDwords() {
return Collections.unmodifiableList(dwords);
}
@Override
public String toString() {
String out = "PodInfoOlderPulseLog{" +
"dwords=[";
List<String> hexDwords = new ArrayList<>();
for (byte[] dword : dwords) {
hexDwords.add(ByteUtil.shortHexStringWithoutSpaces(dword));
}
out += TextUtils.join(", ", hexDwords);
out += "]}";
return out;
}
}

View file

@ -0,0 +1,64 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class PodInfoRecentPulseLog extends PodInfo {
private static final int MINIMUM_MESSAGE_LENGTH = 3;
private final ArrayList<byte[]> dwords;
private final int lastEntryIndex;
public PodInfoRecentPulseLog(byte[] encodedData, int bodyLength) {
super(encodedData);
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
throw new IllegalArgumentException("Not enough data");
}
lastEntryIndex = ByteUtil.toInt(encodedData[1], encodedData[2]);
dwords = new ArrayList<>();
int numberOfDwords = (bodyLength - 3) / 4;
for (int i = 0; numberOfDwords > i; i++) {
byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4);
dwords.add(dword);
}
}
@Override
public PodInfoType getType() {
return PodInfoType.RECENT_PULSE_LOG;
}
public List<byte[]> getDwords() {
return Collections.unmodifiableList(dwords);
}
public int getLastEntryIndex() {
return lastEntryIndex;
}
@Override
public String toString() {
String out = "PodInfoRecentPulseLog{" +
"lastEntryIndex=" + lastEntryIndex +
",dwords=[";
List<String> hexDwords = new ArrayList<>();
for (byte[] dword : dwords) {
hexDwords.add(ByteUtil.shortHexStringWithoutSpaces(dword));
}
out += TextUtils.join(", ", hexDwords);
out += "]}";
return out;
}
}

View file

@ -0,0 +1,40 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
public class PodInfoResponse extends MessageBlock {
private final PodInfoType subType;
private final PodInfo podInfo;
public PodInfoResponse(byte[] encodedData) {
int bodyLength = ByteUtil.convertUnsignedByteToInt(encodedData[1]);
this.encodedData = ByteUtil.substring(encodedData, 2, bodyLength);
subType = PodInfoType.fromByte(encodedData[2]);
podInfo = subType.decode(this.encodedData, bodyLength);
}
public PodInfoType getSubType() {
return subType;
}
public <T extends PodInfo> T getPodInfo() {
return (T) podInfo;
}
@Override
public MessageBlockType getType() {
return MessageBlockType.POD_INFO_RESPONSE;
}
@Override
public String toString() {
return "PodInfoResponse{" +
"subType=" + subType.name() +
", podInfo=" + podInfo.toString() +
'}';
}
}

View file

@ -0,0 +1,71 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
import org.joda.time.Duration;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class AlertConfiguration {
private final AlertType alertType;
private final AlertSlot alertSlot;
private final boolean active;
private final boolean autoOffModifier;
private final Duration duration;
private final AlertTrigger<?> alertTrigger;
private final BeepRepeat beepRepeat;
private final BeepType beepType;
public AlertConfiguration(AlertType alertType, AlertSlot alertSlot, boolean active, boolean autoOffModifier,
Duration duration, AlertTrigger alertTrigger,
BeepType beepType, BeepRepeat beepRepeat) {
this.alertType = alertType;
this.alertSlot = alertSlot;
this.active = active;
this.autoOffModifier = autoOffModifier;
this.duration = duration;
this.alertTrigger = alertTrigger;
this.beepRepeat = beepRepeat;
this.beepType = beepType;
}
public AlertType getAlertType() {
return alertType;
}
public AlertSlot getAlertSlot() {
return alertSlot;
}
public byte[] getRawData() {
int firstByte = (alertSlot.getValue() << 4);
firstByte += active ? (1 << 3) : 0;
if (alertTrigger instanceof UnitsRemainingAlertTrigger) {
firstByte += 1 << 2;
}
if (autoOffModifier) {
firstByte += 1 << 1;
}
firstByte += ((int) duration.getStandardMinutes() >>> 8) & 0x1;
byte[] encodedData = new byte[]{
(byte) firstByte,
(byte) duration.getStandardMinutes()
};
if (alertTrigger instanceof UnitsRemainingAlertTrigger) {
int ticks = (int) (((UnitsRemainingAlertTrigger) alertTrigger).getValue() / OmnipodConst.POD_PULSE_SIZE / 2);
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(ticks));
} else if (alertTrigger instanceof TimerAlertTrigger) {
int durationInMinutes = (int) ((TimerAlertTrigger) alertTrigger).getValue().getStandardMinutes();
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(durationInMinutes));
}
encodedData = ByteUtil.concat(encodedData, beepRepeat.getValue());
encodedData = ByteUtil.concat(encodedData, beepType.getValue());
return encodedData;
}
}

View file

@ -0,0 +1,32 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
import org.joda.time.Duration;
public class AlertConfigurationFactory {
public static AlertConfiguration createLowReservoirAlertConfiguration(Double units) {
return new AlertConfiguration(AlertType.LOW_RESERVOIR_ALERT, AlertSlot.SLOT4, true, false, Duration.ZERO,
new UnitsRemainingAlertTrigger(units), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_60_MINUTES);
}
public static AlertConfiguration createExpirationAdvisoryAlertConfiguration(Duration timeUntilAlert, Duration duration) {
return new AlertConfiguration(AlertType.EXPIRATION_ADVISORY_ALERT, AlertSlot.SLOT7, true, false, duration,
new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES);
}
public static AlertConfiguration createShutdownImminentAlertConfiguration(Duration timeUntilAlert) {
return new AlertConfiguration(AlertType.SHUTDOWN_IMMINENT_ALARM, AlertSlot.SLOT2, true, false, Duration.ZERO,
new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_15_MINUTES);
}
public static AlertConfiguration createAutoOffAlertConfiguration(boolean active, Duration countdownDuration) {
return new AlertConfiguration(AlertType.AUTO_OFF_ALARM, AlertSlot.SLOT0, active, true,
Duration.standardMinutes(15), new TimerAlertTrigger(countdownDuration),
BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_15_MINUTES);
}
public static AlertConfiguration createFinishSetupReminderAlertConfiguration() {
return new AlertConfiguration(AlertType.FINISH_SETUP_REMINDER, AlertSlot.SLOT7, true, false,
Duration.standardMinutes(55), new TimerAlertTrigger(Duration.standardMinutes(5)),
BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_5_MINUTES);
}
}

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
import java.util.ArrayList;
import java.util.List;
public class AlertSet {
private final List<AlertSlot> alertSlots;
public AlertSet(byte rawValue) {
alertSlots = new ArrayList<>();
for (AlertSlot alertSlot : AlertSlot.values()) {
if ((alertSlot.getBitMaskValue() & rawValue) != 0) {
alertSlots.add(alertSlot);
}
}
}
public AlertSet(List<AlertSlot> alertSlots) {
this.alertSlots = alertSlots;
}
public List<AlertSlot> getAlertSlots() {
return new ArrayList<>(alertSlots);
}
public int size() {
return alertSlots.size();
}
public byte getRawValue() {
byte value = 0;
for (AlertSlot alertSlot : alertSlots) {
value |= alertSlot.getBitMaskValue();
}
return value;
}
@Override
public String toString() {
return "AlertSet{" +
"alertSlots=" + alertSlots +
'}';
}
}

View file

@ -0,0 +1,35 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public enum AlertSlot {
SLOT0((byte) 0x00),
SLOT1((byte) 0x01),
SLOT2((byte) 0x02),
SLOT3((byte) 0x03),
SLOT4((byte) 0x04),
SLOT5((byte) 0x05),
SLOT6((byte) 0x06),
SLOT7((byte) 0x07);
private byte value;
AlertSlot(byte value) {
this.value = value;
}
public static AlertSlot fromByte(byte value) {
for (AlertSlot type : values()) {
if (type.value == value) {
return type;
}
}
throw new IllegalArgumentException("Unknown AlertSlot: " + value);
}
public byte getBitMaskValue() {
return (byte) (1 << value);
}
public byte getValue() {
return value;
}
}

View file

@ -0,0 +1,14 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public abstract class AlertTrigger<T> {
protected T value;
public AlertTrigger(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}

View file

@ -0,0 +1,11 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public enum AlertType {
FINISH_PAIRING_REMINDER,
FINISH_SETUP_REMINDER,
EXPIRATION_ALERT,
EXPIRATION_ADVISORY_ALERT,
SHUTDOWN_IMMINENT_ALARM,
LOW_RESERVOIR_ALERT,
AUTO_OFF_ALARM
}

View file

@ -0,0 +1,41 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
// BeepConfigType is used only for the $1E Beep Config Command.
public enum BeepConfigType {
// 0x0 always returns an error response for Beep Config (use 0xF for no beep)
BEEP_BEEP_BEEP_BEEP((byte) 0x01),
BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP((byte) 0x02),
BIP_BIP((byte) 0x03),
BEEP((byte) 0x04),
BEEP_BEEP_BEEP((byte) 0x05),
BEEEEEEP((byte) 0x06),
BIP_BIP_BIP_BIP_BIP_BIP((byte) 0x07),
BEEEP_BEEEP((byte) 0x08),
// 0x9 and 0xA always return an error response for Beep Config
BEEP_BEEP((byte) 0xB),
BEEEP((byte) 0xC),
BIP_BEEEEEP((byte) 0xD),
FIVE_SECONDS_BEEP((byte) 0xE), // can only be used if Pod is currently suspended
NO_BEEP((byte) 0xF);
private byte value;
BeepConfigType(byte value) {
this.value = value;
}
public static BeepConfigType fromByte(byte value) {
for (BeepConfigType type : values()) {
if (type.value == value) {
return type;
}
}
throw new IllegalArgumentException("Unknown BeepConfigType: " + value);
}
public byte getValue() {
return value;
}
}

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public enum BeepRepeat {
ONCE((byte) 0x00),
EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_60_MINUTES((byte) 0x01),
EVERY_MINUTE_FOR_15_MINUTES((byte) 0x02),
EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES((byte) 0x03),
EVERY_3_MINUTES_DELAYED((byte) 0x04),
EVERY_60_MINUTES((byte) 0x05),
EVERY_15_MINUTES((byte) 0x06),
EVERY_15_MINUTES_DELAYED((byte) 0x07),
EVERY_5_MINUTES((byte) 0x08);
private byte value;
BeepRepeat(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
}

View file

@ -0,0 +1,34 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
// BeepType is used for the $19 Configure Alerts and $1F Cancel Commands
public enum BeepType {
NO_BEEP((byte) 0x00),
BEEP_BEEP_BEEP_BEEP((byte) 0x01),
BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP((byte) 0x02),
BIP_BIP((byte) 0x03),
BEEP((byte) 0x04),
BEEP_BEEP_BEEP((byte) 0x05),
BEEEEEEP((byte) 0x06),
BIP_BIP_BIP_BIP_BIP_BIP((byte) 0x07),
BEEEP_BEEEP((byte) 0x08);
private byte value;
BeepType(byte value) {
this.value = value;
}
public static BeepType fromByte(byte value) {
for (BeepType type : values()) {
if (type.value == value) {
return type;
}
}
throw new IllegalArgumentException("Unknown BeepType: " + value);
}
public byte getValue() {
return value;
}
}

View file

@ -0,0 +1,33 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public enum DeliveryStatus {
SUSPENDED((byte) 0x00),
NORMAL((byte) 0x01),
TEMP_BASAL_RUNNING((byte) 0x02),
PRIMING((byte) 0x04),
BOLUS_IN_PROGRESS((byte) 0x05),
BOLUS_AND_TEMP_BASAL((byte) 0x06);
private byte value;
DeliveryStatus(byte value) {
this.value = value;
}
public static DeliveryStatus fromByte(byte value) {
for (DeliveryStatus type : values()) {
if (type.value == value) {
return type;
}
}
throw new IllegalArgumentException("Unknown DeliveryStatus: " + value);
}
public byte getValue() {
return value;
}
public boolean isBolusing() {
return this.equals(BOLUS_IN_PROGRESS) || this.equals(BOLUS_AND_TEMP_BASAL);
}
}

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public enum DeliveryType {
NONE((byte) 0x00),
BASAL((byte) 0x01),
TEMP_BASAL((byte) 0x02),
BOLUS((byte) 0x04);
private byte value;
DeliveryType(byte value) {
this.value = value;
}
public static DeliveryType fromByte(byte value) {
for (DeliveryType type : values()) {
if (type.value == value) {
return type;
}
}
throw new IllegalArgumentException("Unknown DeliveryType: " + value);
}
public byte getValue() {
return value;
}
}

Some files were not shown because too many files have changed in this diff Show more