Finish program temp basal command for Omnipod Dash

This commit is contained in:
Bart Sopers 2021-02-18 10:42:50 +01:00
parent 5347ae4f2d
commit d142da37e8
7 changed files with 48 additions and 87 deletions

View file

@ -41,6 +41,11 @@ android {
dependencies {
implementation project(':core')
implementation "org.apache.commons:commons-lang3:$commonslang3_version"
implementation "commons-codec:commons-codec:$commonscodec_version"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "com.google.android.material:material:$material_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
annotationProcessor "com.google.dagger:dagger-android-processor:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

View file

@ -18,36 +18,17 @@ public final class ProgramTempBasalCommand extends HeaderEnabledCommand {
private final ProgramInsulinCommand interlockCommand;
private final ProgramReminder programReminder;
private final List<BasalInsulinProgramElement> insulinProgramElements;
private final TempBasalMethod tempBasalMethod;
protected ProgramTempBasalCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag, ProgramReminder programReminder,
List<BasalInsulinProgramElement> insulinProgramElements, TempBasalMethod tempBasalMethod) {
protected ProgramTempBasalCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag,
ProgramReminder programReminder, List<BasalInsulinProgramElement> insulinProgramElements) {
super(CommandType.PROGRAM_TEMP_BASAL, uniqueId, sequenceNumber, multiCommandFlag);
this.interlockCommand = interlockCommand;
this.programReminder = programReminder;
this.insulinProgramElements = new ArrayList<>(insulinProgramElements);
this.tempBasalMethod = tempBasalMethod;
}
public TempBasalMethod getTempBasalMethod() {
return tempBasalMethod;
}
public byte getBodyLength() {
byte bodyLength = (byte) (insulinProgramElements.size() * 6 + 8);
if (tempBasalMethod == TempBasalMethod.SECOND_METHOD) {
return bodyLength;
}
// TempBasalMethod.FIRST_METHOD
for (BasalInsulinProgramElement element : insulinProgramElements) {
if (element.getTotalTenthPulses() == 0 && element.getNumberOfSlots() > 1) {
bodyLength = (byte) ((element.getNumberOfSlots() - 1) * 6 + bodyLength);
}
}
return bodyLength;
return (byte) (insulinProgramElements.size() * 6 + 8);
}
public short getLength() {
@ -61,15 +42,11 @@ public final class ProgramTempBasalCommand extends HeaderEnabledCommand {
int delayUntilNextTenthPulseInUsec;
if (firstProgramElement.getTotalTenthPulses() == 0) {
if (tempBasalMethod == TempBasalMethod.FIRST_METHOD) {
remainingTenthPulsesInFirstElement = 0;
} else {
remainingTenthPulsesInFirstElement = firstProgramElement.getNumberOfSlots();
}
delayUntilNextTenthPulseInUsec = ProgramBasalUtil.NUMBER_OF_USEC_IN_SLOT;
remainingTenthPulsesInFirstElement = firstProgramElement.getNumberOfSlots();
delayUntilNextTenthPulseInUsec = ProgramBasalUtil.MAX_DELAY_BETWEEN_TENTH_PULSES_IN_USEC_AND_USECS_IN_BASAL_SLOT;
} else {
remainingTenthPulsesInFirstElement = firstProgramElement.getTotalTenthPulses();
delayUntilNextTenthPulseInUsec = (int) (firstProgramElement.getNumberOfSlots() * 1_800.0d / remainingTenthPulsesInFirstElement * 1_000_000);
delayUntilNextTenthPulseInUsec = (int) ((long) firstProgramElement.getNumberOfSlots() * 1_800.0d / remainingTenthPulsesInFirstElement * 1_000_000);
}
ByteBuffer buffer = ByteBuffer.allocate(getLength()) //
@ -129,24 +106,18 @@ public final class ProgramTempBasalCommand extends HeaderEnabledCommand {
throw new IllegalArgumentException("durationInMinutes can not be null");
}
byte durationInSlots = (byte) (durationInMinutes % 30);
byte durationInSlots = (byte) (durationInMinutes / 30);
short[] pulsesPerSlot = ProgramTempBasalUtil.mapTempBasalToPulsesPerSlot(durationInSlots, rateInUnitsPerHour);
short[] tenthPulsesPerSlot = ProgramTempBasalUtil.mapTempBasalToTenthPulsesPerSlot(durationInSlots, rateInUnitsPerHour);
TempBasalMethod tempBasalMethod = tenthPulsesPerSlot[0] == 0 ? TempBasalMethod.SECOND_METHOD : TempBasalMethod.FIRST_METHOD;
List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramTempBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot);
List<BasalInsulinProgramElement> insulinProgramElements = ProgramTempBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, tempBasalMethod);
List<BasalInsulinProgramElement> insulinProgramElements = ProgramTempBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot);
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, shortInsulinProgramElements,
ProgramTempBasalUtil.calculateChecksum(durationInSlots, pulsesPerSlot[0], pulsesPerSlot), durationInSlots,
(short) 0x3840, pulsesPerSlot[0], ProgramInsulinCommand.DeliveryType.TEMP_BASAL);
return new ProgramTempBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, programReminder, insulinProgramElements, tempBasalMethod);
return new ProgramTempBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, programReminder, insulinProgramElements);
}
}
public enum TempBasalMethod {
FIRST_METHOD,
SECOND_METHOD
}
}

View file

@ -4,23 +4,23 @@ import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable;
import static info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util.ProgramBasalUtil.MAX_DELAY_BETWEEN_TENTH_PULSES_IN_USEC_AND_USECS_IN_BASAL_SLOT;
public class BasalInsulinProgramElement implements Encodable {
private final byte startSlotIndex;
private final byte numberOfSlots;
private final short totalTenthPulses;
private final int delayBetweenTenthPulsesInUsec;
public BasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec) {
public BasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses) {
this.startSlotIndex = startSlotIndex;
this.numberOfSlots = numberOfSlots;
this.totalTenthPulses = totalTenthPulses;
this.delayBetweenTenthPulsesInUsec = delayBetweenTenthPulsesInUsec;
}
@Override public byte[] getEncoded() {
return ByteBuffer.allocate(6) //
.putShort(totalTenthPulses) //
.putInt(delayBetweenTenthPulsesInUsec) //
.putInt(totalTenthPulses == 0 ? Integer.MIN_VALUE | getDelayBetweenTenthPulsesInUsec() : getDelayBetweenTenthPulsesInUsec()) //
.array();
}
@ -41,7 +41,10 @@ public class BasalInsulinProgramElement implements Encodable {
}
public int getDelayBetweenTenthPulsesInUsec() {
return delayBetweenTenthPulsesInUsec;
if (totalTenthPulses == 0) {
return MAX_DELAY_BETWEEN_TENTH_PULSES_IN_USEC_AND_USECS_IN_BASAL_SLOT;
}
return (int) (((long) MAX_DELAY_BETWEEN_TENTH_PULSES_IN_USEC_AND_USECS_IN_BASAL_SLOT * numberOfSlots) / (double) totalTenthPulses);
}
@Override public String toString() {
@ -49,7 +52,7 @@ public class BasalInsulinProgramElement implements Encodable {
"startSlotIndex=" + startSlotIndex +
", numberOfSlots=" + numberOfSlots +
", totalTenthPulses=" + totalTenthPulses +
", delayBetweenTenthPulsesInUsec=" + delayBetweenTenthPulsesInUsec +
", delayBetweenTenthPulsesInUsec=" + getDelayBetweenTenthPulsesInUsec() +
'}';
}
}

View file

@ -2,29 +2,17 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.
import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.ProgramTempBasalCommand;
public class TempBasalInsulinProgramElement extends BasalInsulinProgramElement {
private final ProgramTempBasalCommand.TempBasalMethod tempBasalMethod;
public TempBasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec, ProgramTempBasalCommand.TempBasalMethod tempBasalMethod) {
super(startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec);
this.tempBasalMethod = tempBasalMethod;
public TempBasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses) {
super(startSlotIndex, numberOfSlots, totalTenthPulses);
}
@Override public byte[] getEncoded() {
ByteBuffer buffer = ByteBuffer.allocate(6);
if (getTotalTenthPulses() == 0) {
if (tempBasalMethod == ProgramTempBasalCommand.TempBasalMethod.FIRST_METHOD) {
for (int i = 0; i < getNumberOfSlots(); i++) {
buffer.putShort((short) 0) //
.putInt((int) ((long) getDurationInSeconds() * 1_000_000d / getNumberOfSlots()));
}
} else {
// Zero basal and temp basal second method
buffer.putShort(getNumberOfSlots()) //
.putInt((int) ((long) getDurationInSeconds() * 1_000_000d / getNumberOfSlots()));
}
int i = ((int) ((((double) getDurationInSeconds()) * 1_000_000.0d) / ((double) getNumberOfSlots()))) | Integer.MIN_VALUE;
buffer.putShort(getNumberOfSlots()) //
.putInt(i);
} else {
buffer.putShort(getTotalTenthPulses()) //
.putInt(getDelayBetweenTenthPulsesInUsec());

View file

@ -15,7 +15,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil;
public final class ProgramBasalUtil {
public static final int NUMBER_OF_USEC_IN_SLOT = 1_800_000_000;
public static final int MAX_DELAY_BETWEEN_TENTH_PULSES_IN_USEC_AND_USECS_IN_BASAL_SLOT = 1_800_000_000;
public static final byte NUMBER_OF_BASAL_SLOTS = 48;
public static final byte MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT = 16;
@ -23,7 +24,7 @@ public final class ProgramBasalUtil {
}
public interface BasalInsulinProgramElementFactory<T extends BasalInsulinProgramElement> {
T create(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec);
T create(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses);
}
public static List<BasalInsulinProgramElement> mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) {
@ -45,7 +46,7 @@ public final class ProgramBasalUtil {
previousTenthPulsesPerSlot = tenthPulsesPerSlot[i];
numberOfSlotsInCurrentElement = 1;
} else if (previousTenthPulsesPerSlot != tenthPulsesPerSlot[i] || (numberOfSlotsInCurrentElement + 1) * previousTenthPulsesPerSlot > 65_534) {
elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement))));
elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)));
previousTenthPulsesPerSlot = tenthPulsesPerSlot[i];
numberOfSlotsInCurrentElement = 1;
@ -54,7 +55,7 @@ public final class ProgramBasalUtil {
numberOfSlotsInCurrentElement++;
}
}
elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement))));
elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)));
return elements;
}
@ -83,7 +84,7 @@ public final class ProgramBasalUtil {
if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT) {
numberOfSlotsInCurrentElement++;
} else {
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
@ -95,6 +96,7 @@ public final class ProgramBasalUtil {
boolean expectAlternatePulseForNextSegment = false;
currentTotalNumberOfSlots++;
numberOfSlotsInCurrentElement++;
extraAlternatePulse = true;
while (currentTotalNumberOfSlots < pulsesPerSlot.length) {
// Loop rest alternate pulse segment
@ -110,7 +112,7 @@ public final class ProgramBasalUtil {
} else {
// End of alternate pulse segment (no slots left in element)
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
@ -119,7 +121,7 @@ public final class ProgramBasalUtil {
} else {
// End of alternate pulse segment (new number of pulses per slot)
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
@ -129,7 +131,7 @@ public final class ProgramBasalUtil {
}
} else if (previousPulsesPerSlot != pulsesPerSlot[currentTotalNumberOfSlots]) {
// End of segment (new number of pulses per slot)
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
currentTotalNumberOfSlots++;

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.
import java.nio.ByteBuffer;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.ProgramTempBasalCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.BasalInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.TempBasalInsulinProgramElement;
@ -13,10 +12,8 @@ public final class ProgramTempBasalUtil {
private ProgramTempBasalUtil() {
}
public static List<BasalInsulinProgramElement> mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot, ProgramTempBasalCommand.TempBasalMethod tempBasalMethod) {
return ProgramBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot,
(startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec) ->
new TempBasalInsulinProgramElement(startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec, tempBasalMethod));
public static List<BasalInsulinProgramElement> mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) {
return ProgramBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, TempBasalInsulinProgramElement::new);
}
public static short[] mapTempBasalToTenthPulsesPerSlot(int durationInSlots, double rateInUnitsPerHour) {

View file

@ -7,38 +7,33 @@ import org.junit.Test;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public class ProgramTempBasalCommandTest {
@Test
public void testFirstTempBasalMethod() throws DecoderException {
public void testAlternateSegmentTempBasal() throws DecoderException {
ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() //
.setUniqueId(37879809) //
.setNonce(1229869870) //
.setSequenceNumber((short) 3) //
.setRateInUnitsPerHour(5d) //
.setSequenceNumber((short) 15) //
.setRateInUnitsPerHour(5.05d) //
.setDurationInMinutes((short) 60) //
.setProgramReminder(new ProgramReminder(false, true, (byte) 0)) //
.build();
assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod());
assertArrayEquals(Hex.decodeHex("024200010C201A0E494E532E01014303384000322032160E400005DC0036EE8005DC0036EE808396"), command.getEncoded());
assertArrayEquals(Hex.decodeHex("024200013C201A0E494E532E01011102384000321832160E400003F20036634403F20036634482A6"), command.getEncoded());
}
@Test
public void testSecondTempBasalMethod() throws DecoderException {
public void testZeroTempBasal() throws DecoderException {
ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() //
.setUniqueId(37879809) //
.setNonce(1229869870) //
.setSequenceNumber((short) 13) //
.setSequenceNumber((short) 7) //
.setRateInUnitsPerHour(0.0) //
.setDurationInMinutes((short) 60) //
.setDurationInMinutes((short) 300) //
.setProgramReminder(new ProgramReminder(true, true, (byte) 0)) //
.build();
assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod());
assertArrayEquals(Hex.decodeHex("0242000134201A0E494E532E01007B03384000002000160EC00000036B49D2000003EB49D2000223"), command.getEncoded());
assertArrayEquals(Hex.decodeHex("024200011C201A0E494E532E0100820A384000009000160EC000000A6B49D200000AEB49D20001DE"), command.getEncoded());
}
}