WIP on Omnipod program temp basal command

This commit is contained in:
Bart Sopers 2021-02-18 00:31:25 +01:00
parent e961a7990e
commit 5347ae4f2d
11 changed files with 351 additions and 32 deletions

View file

@ -9,10 +9,10 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.b
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.builder.NonceEnabledCommandBuilder; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.builder.NonceEnabledCommandBuilder;
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.BasalInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentLongInsulinProgramElement; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentBasalInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentSlot; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentSlot;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ProgramBasalUtil;
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.ShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util.ProgramBasalUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder;
@ -115,18 +115,18 @@ public final class ProgramBasalCommand extends HeaderEnabledCommand {
short[] pulsesPerSlot = ProgramBasalUtil.mapBasalProgramToPulsesPerSlot(basalProgram); short[] pulsesPerSlot = ProgramBasalUtil.mapBasalProgramToPulsesPerSlot(basalProgram);
CurrentSlot currentSlot = ProgramBasalUtil.calculateCurrentSlot(pulsesPerSlot, currentTime); CurrentSlot currentSlot = ProgramBasalUtil.calculateCurrentSlot(pulsesPerSlot, currentTime);
short checksum = ProgramBasalUtil.createChecksum(pulsesPerSlot, currentSlot); short checksum = ProgramBasalUtil.calculateChecksum(pulsesPerSlot, currentSlot);
List<BasalInsulinProgramElement> longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram)); List<BasalInsulinProgramElement> longInsulinProgramElements = ProgramBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram));
List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot);
CurrentLongInsulinProgramElement currentLongInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime); CurrentBasalInsulinProgramElement currentBasalInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime);
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce,
shortInsulinProgramElements, checksum, currentSlot.getIndex(), currentSlot.getEighthSecondsRemaining(), shortInsulinProgramElements, checksum, currentSlot.getIndex(), currentSlot.getEighthSecondsRemaining(),
currentSlot.getPulsesRemaining(), ProgramInsulinCommand.DeliveryType.BASAL); currentSlot.getPulsesRemaining(), ProgramInsulinCommand.DeliveryType.BASAL);
return new ProgramBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, return new ProgramBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag,
longInsulinProgramElements, programReminder, currentLongInsulinProgramElement.getIndex(), longInsulinProgramElements, programReminder, currentBasalInsulinProgramElement.getIndex(),
currentLongInsulinProgramElement.getRemainingTenthPulses(), currentLongInsulinProgramElement.getDelayUntilNextTenthPulseInUsec()); currentBasalInsulinProgramElement.getRemainingTenthPulses(), currentBasalInsulinProgramElement.getDelayUntilNextTenthPulseInUsec());
} }
} }
} }

View file

@ -103,7 +103,7 @@ public final class ProgramBolusCommand extends HeaderEnabledCommand {
short byte10And11 = (short) (numberOfPulses * delayBetweenPulsesInEighthSeconds); short byte10And11 = (short) (numberOfPulses * delayBetweenPulsesInEighthSeconds);
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce,
Collections.singletonList(new BolusShortInsulinProgramElement(numberOfPulses)), createChecksum((byte) 0x01, byte10And11, numberOfPulses), Collections.singletonList(new BolusShortInsulinProgramElement(numberOfPulses)), calculateChecksum((byte) 0x01, byte10And11, numberOfPulses),
(byte) 0x01, byte10And11, (short) numberOfPulses, ProgramInsulinCommand.DeliveryType.BOLUS); (byte) 0x01, byte10And11, (short) numberOfPulses, ProgramInsulinCommand.DeliveryType.BOLUS);
int delayUntilFirstTenthPulseInUsec = delayBetweenPulsesInEighthSeconds / 8 * 100_000; int delayUntilFirstTenthPulseInUsec = delayBetweenPulsesInEighthSeconds / 8 * 100_000;
@ -112,8 +112,8 @@ public final class ProgramBolusCommand extends HeaderEnabledCommand {
} }
} }
private static short createChecksum(byte numberOfSlots, short byte10And11, short numberOfPulses) { private static short calculateChecksum(byte numberOfSlots, short byte10And11, short numberOfPulses) {
return MessageUtil.createCheckSum(ByteBuffer.allocate(7) // return MessageUtil.calculateChecksum(ByteBuffer.allocate(7) //
.put(numberOfSlots) // .put(numberOfSlots) //
.putShort(byte10And11) // .putShort(byte10And11) //
.putShort(numberOfPulses) // .putShort(numberOfPulses) //

View file

@ -69,7 +69,7 @@ final class ProgramInsulinCommand extends NonceEnabledCommand {
} }
} }
public short createChecksum(byte[] bytes) { public short calculateChecksum(byte[] bytes) {
short sum = 0; short sum = 0;
for (byte b : bytes) { for (byte b : bytes) {
sum += (short) (b & 0xff); sum += (short) (b & 0xff);

View file

@ -0,0 +1,152 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.builder.NonceEnabledCommandBuilder;
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.util.ProgramBasalUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util.ProgramTempBasalUtil;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder;
// NOT SUPPORTED: percentage temp basal
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) {
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;
}
public short getLength() {
return (short) (getBodyLength() + 2);
}
@Override public byte[] getEncoded() {
BasalInsulinProgramElement firstProgramElement = insulinProgramElements.get(0);
short remainingTenthPulsesInFirstElement;
int delayUntilNextTenthPulseInUsec;
if (firstProgramElement.getTotalTenthPulses() == 0) {
if (tempBasalMethod == TempBasalMethod.FIRST_METHOD) {
remainingTenthPulsesInFirstElement = 0;
} else {
remainingTenthPulsesInFirstElement = firstProgramElement.getNumberOfSlots();
}
delayUntilNextTenthPulseInUsec = ProgramBasalUtil.NUMBER_OF_USEC_IN_SLOT;
} else {
remainingTenthPulsesInFirstElement = firstProgramElement.getTotalTenthPulses();
delayUntilNextTenthPulseInUsec = (int) (firstProgramElement.getNumberOfSlots() * 1_800.0d / remainingTenthPulsesInFirstElement * 1_000_000);
}
ByteBuffer buffer = ByteBuffer.allocate(getLength()) //
.put(commandType.getValue()) //
.put(getBodyLength()) //
.put(programReminder.getEncoded()) //
.put((byte) 0x00) // Current slot index
.putShort(remainingTenthPulsesInFirstElement) //
.putInt(delayUntilNextTenthPulseInUsec);
for (BasalInsulinProgramElement element : insulinProgramElements) {
buffer.put(element.getEncoded());
}
byte[] tempBasalCommand = buffer.array();
byte[] interlockCommand = this.interlockCommand.getEncoded();
byte[] header = encodeHeader(uniqueId, sequenceNumber, (short) (tempBasalCommand.length + interlockCommand.length), multiCommandFlag);
return appendCrc(ByteBuffer.allocate(header.length + interlockCommand.length + tempBasalCommand.length) //
.put(header) //
.put(interlockCommand) //
.put(tempBasalCommand) //
.array());
}
public static class Builder extends NonceEnabledCommandBuilder<Builder, ProgramTempBasalCommand> {
private ProgramReminder programReminder;
private Double rateInUnitsPerHour;
private Short durationInMinutes;
public Builder setProgramReminder(ProgramReminder programReminder) {
this.programReminder = programReminder;
return this;
}
public Builder setRateInUnitsPerHour(double rateInUnitsPerHour) {
this.rateInUnitsPerHour = rateInUnitsPerHour;
return this;
}
public Builder setDurationInMinutes(short durationInMinutes) {
if (durationInMinutes % 30 != 0) {
throw new IllegalArgumentException("durationInMinutes must be dividable by 30");
}
this.durationInMinutes = durationInMinutes;
return this;
}
@Override protected ProgramTempBasalCommand buildCommand() {
if (programReminder == null) {
throw new IllegalArgumentException("programReminder can not be null");
}
if (rateInUnitsPerHour == null) {
throw new IllegalArgumentException("rateInUnitsPerHour can not be null");
}
if (durationInMinutes == null) {
throw new IllegalArgumentException("durationInMinutes can not be null");
}
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);
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);
}
}
public enum TempBasalMethod {
FIRST_METHOD,
SECOND_METHOD
}
}

View file

@ -32,6 +32,10 @@ public class BasalInsulinProgramElement implements Encodable {
return numberOfSlots; return numberOfSlots;
} }
public short getDurationInSeconds() {
return (short) (numberOfSlots * 1_800);
}
public short getTotalTenthPulses() { public short getTotalTenthPulses() {
return totalTenthPulses; return totalTenthPulses;
} }

View file

@ -1,11 +1,11 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
public class CurrentLongInsulinProgramElement { public class CurrentBasalInsulinProgramElement {
private final byte index; private final byte index;
private final int delayUntilNextTenthPulseInUsec; private final int delayUntilNextTenthPulseInUsec;
private final short remainingTenthPulses; private final short remainingTenthPulses;
public CurrentLongInsulinProgramElement(byte index, int delayUntilNextTenthPulseInUsec, short remainingTenthPulses) { public CurrentBasalInsulinProgramElement(byte index, int delayUntilNextTenthPulseInUsec, short remainingTenthPulses) {
this.index = index; this.index = index;
this.delayUntilNextTenthPulseInUsec = delayUntilNextTenthPulseInUsec; this.delayUntilNextTenthPulseInUsec = delayUntilNextTenthPulseInUsec;
this.remainingTenthPulses = remainingTenthPulses; this.remainingTenthPulses = remainingTenthPulses;

View file

@ -0,0 +1,35 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
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;
}
@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()));
}
} else {
buffer.putShort(getTotalTenthPulses()) //
.putInt(getDelayBetweenTenthPulsesInUsec());
}
return buffer.array();
}
}

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
@ -6,20 +6,33 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
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.BasalShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentBasalInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentSlot;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil;
public final class ProgramBasalUtil { public final class ProgramBasalUtil {
private static final byte NUMBER_OF_BASAL_SLOTS = 48; public static final int NUMBER_OF_USEC_IN_SLOT = 1_800_000_000;
private static final byte MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT = 16; public static final byte NUMBER_OF_BASAL_SLOTS = 48;
private static final int NUMBER_OF_USEC_IN_SLOT = 1_800_000_000; public static final byte MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT = 16;
private ProgramBasalUtil() { private ProgramBasalUtil() {
} }
public static List<BasalInsulinProgramElement> mapPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) { public interface BasalInsulinProgramElementFactory<T extends BasalInsulinProgramElement> {
if (tenthPulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { T create(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec);
throw new IllegalArgumentException("Basal program must contain 48 slots"); }
public static List<BasalInsulinProgramElement> mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) {
return mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, BasalInsulinProgramElement::new);
}
public static <T extends BasalInsulinProgramElement> List<BasalInsulinProgramElement> mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot, BasalInsulinProgramElementFactory<T> insulinProgramElementFactory) {
if (tenthPulsesPerSlot.length > NUMBER_OF_BASAL_SLOTS) {
throw new IllegalArgumentException("Basal program must contain at most 48 slots");
} }
List<BasalInsulinProgramElement> elements = new ArrayList<>(); List<BasalInsulinProgramElement> elements = new ArrayList<>();
@ -27,12 +40,12 @@ public final class ProgramBasalUtil {
byte numberOfSlotsInCurrentElement = 0; byte numberOfSlotsInCurrentElement = 0;
byte startSlotIndex = 0; byte startSlotIndex = 0;
for (int i = 0; i < NUMBER_OF_BASAL_SLOTS; i++) { for (int i = 0; i < tenthPulsesPerSlot.length; i++) {
if (i == 0) { if (i == 0) {
previousTenthPulsesPerSlot = tenthPulsesPerSlot[i]; previousTenthPulsesPerSlot = tenthPulsesPerSlot[i];
numberOfSlotsInCurrentElement = 1; numberOfSlotsInCurrentElement = 1;
} else if (previousTenthPulsesPerSlot != tenthPulsesPerSlot[i] || (numberOfSlotsInCurrentElement + 1) * previousTenthPulsesPerSlot > 65_534) { } else if (previousTenthPulsesPerSlot != tenthPulsesPerSlot[i] || (numberOfSlotsInCurrentElement + 1) * previousTenthPulsesPerSlot > 65_534) {
elements.add(new BasalInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement))));
previousTenthPulsesPerSlot = tenthPulsesPerSlot[i]; previousTenthPulsesPerSlot = tenthPulsesPerSlot[i];
numberOfSlotsInCurrentElement = 1; numberOfSlotsInCurrentElement = 1;
@ -41,14 +54,14 @@ public final class ProgramBasalUtil {
numberOfSlotsInCurrentElement++; numberOfSlotsInCurrentElement++;
} }
} }
elements.add(new BasalInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement))));
return elements; return elements;
} }
public static List<ShortInsulinProgramElement> mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) { public static List<ShortInsulinProgramElement> mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) {
if (pulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { if (pulsesPerSlot.length > NUMBER_OF_BASAL_SLOTS) {
throw new IllegalArgumentException("Basal program must contain 48 slots"); throw new IllegalArgumentException("Basal program must contain at most 48 slots");
} }
List<ShortInsulinProgramElement> elements = new ArrayList<>(); List<ShortInsulinProgramElement> elements = new ArrayList<>();
@ -57,7 +70,7 @@ public final class ProgramBasalUtil {
byte numberOfSlotsInCurrentElement = 0; byte numberOfSlotsInCurrentElement = 0;
byte currentTotalNumberOfSlots = 0; byte currentTotalNumberOfSlots = 0;
while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) { while (currentTotalNumberOfSlots < pulsesPerSlot.length) {
if (currentTotalNumberOfSlots == 0) { if (currentTotalNumberOfSlots == 0) {
// First slot // First slot
@ -83,7 +96,7 @@ public final class ProgramBasalUtil {
boolean expectAlternatePulseForNextSegment = false; boolean expectAlternatePulseForNextSegment = false;
currentTotalNumberOfSlots++; currentTotalNumberOfSlots++;
extraAlternatePulse = true; extraAlternatePulse = true;
while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) { while (currentTotalNumberOfSlots < pulsesPerSlot.length) {
// Loop rest alternate pulse segment // Loop rest alternate pulse segment
if (pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot + (expectAlternatePulseForNextSegment ? 1 : 0)) { if (pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot + (expectAlternatePulseForNextSegment ? 1 : 0)) {
@ -182,7 +195,7 @@ public final class ProgramBasalUtil {
return new CurrentSlot(index, (short) (secondsRemaining * 8), pulsesRemaining); return new CurrentSlot(index, (short) (secondsRemaining * 8), pulsesRemaining);
} }
public static CurrentLongInsulinProgramElement calculateCurrentLongInsulinProgramElement(List<BasalInsulinProgramElement> elements, Date currentTime) { public static CurrentBasalInsulinProgramElement calculateCurrentLongInsulinProgramElement(List<BasalInsulinProgramElement> elements, Date currentTime) {
Calendar instance = Calendar.getInstance(); Calendar instance = Calendar.getInstance();
instance.setTime(currentTime); instance.setTime(currentTime);
@ -218,7 +231,7 @@ public final class ProgramBasalUtil {
} }
short remainingTenthPulses = (short) ((remainingTenThousandthPulses % 1_000 != 0 ? 1 : 0) + remainingTenThousandthPulses / 1_000); short remainingTenthPulses = (short) ((remainingTenThousandthPulses % 1_000 != 0 ? 1 : 0) + remainingTenThousandthPulses / 1_000);
return new CurrentLongInsulinProgramElement(index, delayUntilNextTenthPulseInUsec, remainingTenthPulses); return new CurrentBasalInsulinProgramElement(index, delayUntilNextTenthPulseInUsec, remainingTenthPulses);
} }
index++; index++;
@ -227,7 +240,7 @@ public final class ProgramBasalUtil {
throw new IllegalStateException("Could not determine current long insulin program element"); throw new IllegalStateException("Could not determine current long insulin program element");
} }
public static short createChecksum(short[] pulsesPerSlot, CurrentSlot currentSlot) { public static short calculateChecksum(short[] pulsesPerSlot, CurrentSlot currentSlot) {
ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + 2 + NUMBER_OF_BASAL_SLOTS * 2) // ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + 2 + NUMBER_OF_BASAL_SLOTS * 2) //
.put(currentSlot.getIndex()) // .put(currentSlot.getIndex()) //
.putShort(currentSlot.getPulsesRemaining()) // .putShort(currentSlot.getPulsesRemaining()) //
@ -237,6 +250,6 @@ public final class ProgramBasalUtil {
buffer.putShort(pulses); buffer.putShort(pulses);
} }
return MessageUtil.createCheckSum(buffer.array()); return MessageUtil.calculateChecksum(buffer.array());
} }
} }

View file

@ -0,0 +1,71 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util;
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;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil;
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 short[] mapTempBasalToTenthPulsesPerSlot(int durationInSlots, double rateInUnitsPerHour) {
short pulsesPerHour = (short) Math.round(rateInUnitsPerHour * 20);
short[] tenthPulsesPerSlot = new short[durationInSlots];
for (int i = 0; durationInSlots > i; i++) {
tenthPulsesPerSlot[i] = (short) (roundToHalf(pulsesPerHour / 2.0d) * 10);
}
return tenthPulsesPerSlot;
}
private static double roundToHalf(double d) {
return (double) (short) ((short) (int) (d * 10.0d) / 5 * 5) / 10.0d;
}
public static short[] mapTempBasalToPulsesPerSlot(byte durationInSlots, double rateInUnitsPerHour) {
short pulsesPerHour = (short) Math.round(rateInUnitsPerHour * 20);
short[] pulsesPerSlot = new short[durationInSlots];
boolean remainingPulse = false;
for (int i = 0; durationInSlots > i; i++) {
pulsesPerSlot[i] = (short) (pulsesPerHour / 2);
if (pulsesPerHour % 2 == 1) { // Do extra alternate pulse
if (remainingPulse) {
pulsesPerSlot[i] += 1;
}
remainingPulse = !remainingPulse;
}
}
return pulsesPerSlot;
}
public static short calculateChecksum(byte totalNumberOfSlots, short pulsesInFirstSlot, short[] pulsesPerSlot) {
ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + 2 + 2 * pulsesPerSlot.length) //
.put(totalNumberOfSlots) //
.putShort((short) 0x3840) //
.putShort(pulsesInFirstSlot);
for (short pulses : pulsesPerSlot) {
buffer.putShort(pulses);
}
return MessageUtil.calculateChecksum(buffer.array());
}
public static List<ShortInsulinProgramElement> mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) {
return ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot);
}
}

View file

@ -30,7 +30,7 @@ public class MessageUtil {
return s; return s;
} }
public static short createCheckSum(byte[] bytes) { public static short calculateChecksum(byte[] bytes) {
short sum = 0; short sum = 0;
for (byte b : bytes) { for (byte b : bytes) {
sum += (short) (b & 0xff); sum += (short) (b & 0xff);

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
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 {
ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() //
.setUniqueId(37879809) //
.setNonce(1229869870) //
.setSequenceNumber((short) 3) //
.setRateInUnitsPerHour(5d) //
.setDurationInMinutes((short) 60) //
.setProgramReminder(new ProgramReminder(false, true, (byte) 0)) //
.build();
assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod());
assertArrayEquals(Hex.decodeHex("024200010C201A0E494E532E01014303384000322032160E400005DC0036EE8005DC0036EE808396"), command.getEncoded());
}
@Test
public void testSecondTempBasalMethod() throws DecoderException {
ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() //
.setUniqueId(37879809) //
.setNonce(1229869870) //
.setSequenceNumber((short) 13) //
.setRateInUnitsPerHour(0.0) //
.setDurationInMinutes((short) 60) //
.setProgramReminder(new ProgramReminder(true, true, (byte) 0)) //
.build();
assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod());
assertArrayEquals(Hex.decodeHex("0242000134201A0E494E532E01007B03384000002000160EC00000036B49D2000003EB49D2000223"), command.getEncoded());
}
}