WIP on Omnipod program temp basal command
This commit is contained in:
parent
e961a7990e
commit
5347ae4f2d
11 changed files with 351 additions and 32 deletions
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) //
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue