Add Omnipod Dash bolus command

This commit is contained in:
Bart Sopers 2021-02-17 19:20:27 +01:00
parent cfbd8aeea4
commit 23450aee7e
11 changed files with 261 additions and 77 deletions

View file

@ -8,9 +8,9 @@ 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.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.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.CurrentLongInsulinProgramElement; 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.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.LongInsulinProgramElement;
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.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.definition.BasalProgram; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram;
@ -19,13 +19,13 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio
// Always preceded by 0x1a ProgramInsulinCommand // Always preceded by 0x1a ProgramInsulinCommand
public final class ProgramBasalCommand extends HeaderEnabledCommand { public final class ProgramBasalCommand extends HeaderEnabledCommand {
private final ProgramInsulinCommand interlockCommand; private final ProgramInsulinCommand interlockCommand;
private final List<LongInsulinProgramElement> insulinProgramElements; private final List<BasalInsulinProgramElement> insulinProgramElements;
private final ProgramReminder programReminder; private final ProgramReminder programReminder;
private final byte currentInsulinProgramElementIndex; private final byte currentInsulinProgramElementIndex;
private final short remainingTenthPulsesInCurrentInsulinProgramElement; private final short remainingTenthPulsesInCurrentInsulinProgramElement;
private final int delayUntilNextTenthPulseInUsec; private final int delayUntilNextTenthPulseInUsec;
ProgramBasalCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag, List<LongInsulinProgramElement> insulinProgramElements, ProgramReminder programReminder, byte currentInsulinProgramElementIndex, short remainingTenthPulsesInCurrentInsulinProgramElement, int delayUntilNextTenthPulseInUsec) { ProgramBasalCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag, List<BasalInsulinProgramElement> insulinProgramElements, ProgramReminder programReminder, byte currentInsulinProgramElementIndex, short remainingTenthPulsesInCurrentInsulinProgramElement, int delayUntilNextTenthPulseInUsec) {
super(CommandType.PROGRAM_BASAL, uniqueId, sequenceNumber, multiCommandFlag); super(CommandType.PROGRAM_BASAL, uniqueId, sequenceNumber, multiCommandFlag);
this.interlockCommand = interlockCommand; this.interlockCommand = interlockCommand;
@ -52,18 +52,18 @@ public final class ProgramBasalCommand extends HeaderEnabledCommand {
.put(currentInsulinProgramElementIndex) // .put(currentInsulinProgramElementIndex) //
.putShort(remainingTenthPulsesInCurrentInsulinProgramElement) // .putShort(remainingTenthPulsesInCurrentInsulinProgramElement) //
.putInt(delayUntilNextTenthPulseInUsec); .putInt(delayUntilNextTenthPulseInUsec);
for (LongInsulinProgramElement insulinProgramElement : insulinProgramElements) { for (BasalInsulinProgramElement insulinProgramElement : insulinProgramElements) {
buffer.put(insulinProgramElement.getEncoded()); buffer.put(insulinProgramElement.getEncoded());
} }
byte[] bolusCommand = buffer.array(); byte[] basalCommand = buffer.array();
byte[] interlockCommand = this.interlockCommand.getEncoded(); byte[] interlockCommand = this.interlockCommand.getEncoded();
byte[] header = encodeHeader(uniqueId, sequenceNumber, (short) (bolusCommand.length + interlockCommand.length), multiCommandFlag); byte[] header = encodeHeader(uniqueId, sequenceNumber, (short) (basalCommand.length + interlockCommand.length), multiCommandFlag);
return appendCrc(ByteBuffer.allocate(bolusCommand.length + interlockCommand.length + header.length) // return appendCrc(ByteBuffer.allocate(basalCommand.length + interlockCommand.length + header.length) //
.put(header) // .put(header) //
.put(interlockCommand) // .put(interlockCommand) //
.put(bolusCommand) // .put(basalCommand) //
.array()); .array());
} }
@ -116,12 +116,12 @@ 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.createChecksum(pulsesPerSlot, currentSlot);
List<LongInsulinProgramElement> longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram)); List<BasalInsulinProgramElement> longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram));
List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot);
CurrentLongInsulinProgramElement currentLongInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime); CurrentLongInsulinProgramElement currentLongInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime);
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce,
shortInsulinProgramElements, currentSlot.getIndex(), checksum, 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,

View file

@ -0,0 +1,123 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command;
import java.nio.ByteBuffer;
import java.util.Collections;
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.BolusShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil;
// NOT SUPPORTED: extended bolus
public final class ProgramBolusCommand extends HeaderEnabledCommand {
private static final short LENGTH = 15;
private static final byte BODY_LENGTH = 13;
private final ProgramInsulinCommand interlockCommand;
private final ProgramReminder programReminder;
private final short numberOfTenthPulses;
private final int delayUntilFirstTenthPulseInUsec;
ProgramBolusCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag, ProgramReminder programReminder, short numberOfTenthPulses, int delayUntilFirstTenthPulseInUsec) {
super(CommandType.PROGRAM_BOLUS, uniqueId, sequenceNumber, multiCommandFlag);
this.interlockCommand = interlockCommand;
this.programReminder = programReminder;
this.numberOfTenthPulses = numberOfTenthPulses;
this.delayUntilFirstTenthPulseInUsec = delayUntilFirstTenthPulseInUsec;
}
@Override public byte[] getEncoded() {
byte[] bolusCommand = ByteBuffer.allocate(LENGTH) //
.put(commandType.getValue()) //
.put(BODY_LENGTH) //
.put(programReminder.getEncoded()) //
.putShort(numberOfTenthPulses) //
.putInt(delayUntilFirstTenthPulseInUsec) //
.putShort((short) 0) // Extended bolus pulses
.putInt(0) // Delay between tenth extended pulses in usec
.array();
byte[] interlockCommand = this.interlockCommand.getEncoded();
byte[] header = encodeHeader(uniqueId, sequenceNumber, (short) (bolusCommand.length + interlockCommand.length), multiCommandFlag);
return appendCrc(ByteBuffer.allocate(header.length + interlockCommand.length + bolusCommand.length) //
.put(header) //
.put(interlockCommand) //
.put(bolusCommand) //
.array());
}
@Override public String toString() {
return "ProgramBolusCommand{" +
"interlockCommand=" + interlockCommand +
", programReminder=" + programReminder +
", numberOfTenthPulses=" + numberOfTenthPulses +
", delayUntilFirstTenthPulseInUsec=" + delayUntilFirstTenthPulseInUsec +
", commandType=" + commandType +
", uniqueId=" + uniqueId +
", sequenceNumber=" + sequenceNumber +
", multiCommandFlag=" + multiCommandFlag +
'}';
}
public static final class Builder extends NonceEnabledCommandBuilder<Builder, ProgramBolusCommand> {
private Double numberOfUnits;
private Byte delayBetweenPulsesInEighthSeconds;
private ProgramReminder programReminder;
public Builder setNumberOfUnits(double numberOfUnits) {
if (numberOfUnits <= 0.0D) {
throw new IllegalArgumentException("Number of units should be greater than zero");
}
if ((int) (numberOfUnits * 1000) % 50 != 0) {
throw new IllegalArgumentException("Number of units must be dividable by 0.05");
}
this.numberOfUnits = ((int) (numberOfUnits * 100)) / 100.0d;
return this;
}
public Builder setDelayBetweenPulsesInEighthSeconds(byte delayBetweenPulsesInEighthSeconds) {
this.delayBetweenPulsesInEighthSeconds = delayBetweenPulsesInEighthSeconds;
return this;
}
public Builder setProgramReminder(ProgramReminder programReminder) {
this.programReminder = programReminder;
return this;
}
@Override protected ProgramBolusCommand buildCommand() {
if (numberOfUnits == null) {
throw new IllegalArgumentException("numberOfUnits can not be null");
}
if (delayBetweenPulsesInEighthSeconds == null) {
throw new IllegalArgumentException("delayBetweenPulsesInEighthSeconds can not be null");
}
if (programReminder == null) {
throw new IllegalArgumentException("programReminder can not be null");
}
short numberOfPulses = (short) Math.round(numberOfUnits * 20);
short byte10And11 = (short) (numberOfPulses * delayBetweenPulsesInEighthSeconds);
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce,
Collections.singletonList(new BolusShortInsulinProgramElement(numberOfPulses)), createChecksum((byte) 0x01, byte10And11, numberOfPulses),
(byte) 0x01, byte10And11, (short) numberOfPulses, ProgramInsulinCommand.DeliveryType.BOLUS);
int delayUntilFirstTenthPulseInUsec = delayBetweenPulsesInEighthSeconds / 8 * 100_000;
return new ProgramBolusCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, programReminder, (short) (numberOfPulses * 10), delayUntilFirstTenthPulseInUsec);
}
}
private static short createChecksum(byte numberOfSlots, short byte10And11, short numberOfPulses) {
return MessageUtil.createCheckSum(ByteBuffer.allocate(7) //
.put(numberOfSlots) //
.putShort(byte10And11) //
.putShort(numberOfPulses) //
.putShort(numberOfPulses) //
.array());
}
}

View file

@ -11,19 +11,19 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.i
// Always followed by one of: 0x13, 0x16, 0x17 // Always followed by one of: 0x13, 0x16, 0x17
final class ProgramInsulinCommand extends NonceEnabledCommand { final class ProgramInsulinCommand extends NonceEnabledCommand {
private final List<ShortInsulinProgramElement> insulinProgramElements; private final List<ShortInsulinProgramElement> insulinProgramElements;
private final byte currentSlot;
private final short checksum; private final short checksum;
private final short remainingEighthSecondsInCurrentSlot; private final byte byte9;
private final short remainingPulsesInCurrentSlot; private final short byte10And11;
private final short byte12And13;
private final DeliveryType deliveryType; private final DeliveryType deliveryType;
ProgramInsulinCommand(int uniqueId, short sequenceNumber, boolean multiCommandFlag, int nonce, List<ShortInsulinProgramElement> insulinProgramElements, byte currentSlot, short checksum, short remainingEighthSecondsInCurrentSlot, short remainingPulsesInCurrentSlot, DeliveryType deliveryType) { ProgramInsulinCommand(int uniqueId, short sequenceNumber, boolean multiCommandFlag, int nonce, List<ShortInsulinProgramElement> insulinProgramElements, short checksum, byte byte9, short byte10And11, short byte12And13, DeliveryType deliveryType) {
super(CommandType.PROGRAM_INSULIN, uniqueId, sequenceNumber, multiCommandFlag, nonce); super(CommandType.PROGRAM_INSULIN, uniqueId, sequenceNumber, multiCommandFlag, nonce);
this.insulinProgramElements = new ArrayList<>(insulinProgramElements); this.insulinProgramElements = new ArrayList<>(insulinProgramElements);
this.currentSlot = currentSlot;
this.checksum = checksum; this.checksum = checksum;
this.remainingEighthSecondsInCurrentSlot = remainingEighthSecondsInCurrentSlot; this.byte9 = byte9;
this.remainingPulsesInCurrentSlot = remainingPulsesInCurrentSlot; this.byte10And11 = byte10And11;
this.byte12And13 = byte12And13;
this.deliveryType = deliveryType; this.deliveryType = deliveryType;
} }
@ -42,9 +42,9 @@ final class ProgramInsulinCommand extends NonceEnabledCommand {
.putInt(nonce) // .putInt(nonce) //
.put(deliveryType.getValue()) // .put(deliveryType.getValue()) //
.putShort(checksum) // .putShort(checksum) //
.put(currentSlot) // .put(byte9) // BASAL: currentSlot // BOLUS: number of ShortInsulinProgramElements
.putShort(remainingEighthSecondsInCurrentSlot) // .putShort(byte10And11) // BASAL: remainingEighthSecondsInCurrentSlot // BOLUS: immediate pulses multiplied by delay between pulses in eighth seconds
.putShort(remainingPulsesInCurrentSlot); .putShort(byte12And13); // BASAL: remainingPulsesInCurrentSlot // BOLUS: immediate pulses
for (ShortInsulinProgramElement element : insulinProgramElements) { for (ShortInsulinProgramElement element : insulinProgramElements) {
buffer.put(element.getEncoded()); buffer.put(element.getEncoded());
@ -69,13 +69,21 @@ final class ProgramInsulinCommand extends NonceEnabledCommand {
} }
} }
public short createChecksum(byte[] bytes) {
short sum = 0;
for (byte b : bytes) {
sum += (short) (b & 0xff);
}
return sum;
}
@Override public String toString() { @Override public String toString() {
return "ProgramInsulinCommand{" + return "ProgramInsulinCommand{" +
"insulinProgramElements=" + insulinProgramElements + "insulinProgramElements=" + insulinProgramElements +
", currentSlot=" + currentSlot +
", checksum=" + checksum + ", checksum=" + checksum +
", remainingEighthSecondsInCurrentSlot=" + remainingEighthSecondsInCurrentSlot + ", byte9=" + byte9 +
", remainingPulsesInCurrentSlot=" + remainingPulsesInCurrentSlot + ", byte10And11=" + byte10And11 +
", byte12And13=" + byte12And13 +
", deliveryType=" + deliveryType + ", deliveryType=" + deliveryType +
", nonce=" + nonce + ", nonce=" + nonce +
", commandType=" + commandType + ", commandType=" + commandType +

View file

@ -2,7 +2,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.CrcUtil; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil;
public abstract class HeaderEnabledCommand implements Command { public abstract class HeaderEnabledCommand implements Command {
protected static final short HEADER_LENGTH = 6; protected static final short HEADER_LENGTH = 6;
@ -26,7 +26,7 @@ public abstract class HeaderEnabledCommand implements Command {
protected static byte[] appendCrc(byte[] command) { protected static byte[] appendCrc(byte[] command) {
return ByteBuffer.allocate(command.length + 2) // return ByteBuffer.allocate(command.length + 2) //
.put(command) // .put(command) //
.putShort(CrcUtil.createCrc(command)) // .putShort(MessageUtil.createCrc(command)) //
.array(); .array();
} }

View file

@ -4,13 +4,13 @@ import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable;
public class LongInsulinProgramElement implements Encodable { public class BasalInsulinProgramElement implements Encodable {
private final byte startSlotIndex; private final byte startSlotIndex;
private final byte numberOfSlots; private final byte numberOfSlots;
private final short totalTenthPulses; private final short totalTenthPulses;
private final int delayBetweenTenthPulsesInUsec; private final int delayBetweenTenthPulsesInUsec;
public LongInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec) { public BasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec) {
this.startSlotIndex = startSlotIndex; this.startSlotIndex = startSlotIndex;
this.numberOfSlots = numberOfSlots; this.numberOfSlots = numberOfSlots;
this.totalTenthPulses = totalTenthPulses; this.totalTenthPulses = totalTenthPulses;

View file

@ -0,0 +1,34 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
import java.nio.ByteBuffer;
public class BasalShortInsulinProgramElement implements ShortInsulinProgramElement {
private final byte numberOfSlots; // 4 bits
private final short pulsesPerSlot; // 10 bits
private final boolean extraAlternatePulse;
public BasalShortInsulinProgramElement(byte numberOfSlots, short pulsesPerSlot, boolean extraAlternatePulse) {
this.numberOfSlots = numberOfSlots;
this.pulsesPerSlot = pulsesPerSlot;
this.extraAlternatePulse = extraAlternatePulse;
}
@Override public byte[] getEncoded() {
byte firstByte = (byte) ((((numberOfSlots - 1) & 0x0f) << 4) //
| ((extraAlternatePulse ? 1 : 0) << 3) //
| ((pulsesPerSlot >>> 8) & 0x03));
return ByteBuffer.allocate(2) //
.put(firstByte) //
.put((byte) (pulsesPerSlot & 0xff)) //
.array();
}
@Override public String toString() {
return "ShortInsulinProgramElement{" +
"numberOfSlotsMinusOne=" + numberOfSlots +
", pulsesPerSlot=" + pulsesPerSlot +
", extraAlternatePulse=" + extraAlternatePulse +
'}';
}
}

View file

@ -0,0 +1,19 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
import java.nio.ByteBuffer;
public class BolusShortInsulinProgramElement implements ShortInsulinProgramElement {
private short numberOfPulses;
public BolusShortInsulinProgramElement(short numberOfPulses) {
this.numberOfPulses = numberOfPulses;
}
public short getNumberOfPulses() {
return numberOfPulses;
}
@Override public byte[] getEncoded() {
return ByteBuffer.allocate(2).putShort(numberOfPulses).array();
}
}

View file

@ -7,6 +7,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
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;
public final class ProgramBasalUtil { public final class ProgramBasalUtil {
private static final byte NUMBER_OF_BASAL_SLOTS = 48; private static final byte NUMBER_OF_BASAL_SLOTS = 48;
@ -16,12 +17,12 @@ public final class ProgramBasalUtil {
private ProgramBasalUtil() { private ProgramBasalUtil() {
} }
public static List<LongInsulinProgramElement> mapPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) { public static List<BasalInsulinProgramElement> mapPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) {
if (tenthPulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { if (tenthPulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) {
throw new IllegalArgumentException("Basal program must contain 48 slots"); throw new IllegalArgumentException("Basal program must contain 48 slots");
} }
List<LongInsulinProgramElement> elements = new ArrayList<>(); List<BasalInsulinProgramElement> elements = new ArrayList<>();
long previousTenthPulsesPerSlot = 0; long previousTenthPulsesPerSlot = 0;
byte numberOfSlotsInCurrentElement = 0; byte numberOfSlotsInCurrentElement = 0;
byte startSlotIndex = 0; byte startSlotIndex = 0;
@ -31,7 +32,7 @@ public final class ProgramBasalUtil {
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 LongInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); elements.add(new BasalInsulinProgramElement(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;
@ -40,7 +41,7 @@ public final class ProgramBasalUtil {
numberOfSlotsInCurrentElement++; numberOfSlotsInCurrentElement++;
} }
} }
elements.add(new LongInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); elements.add(new BasalInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement))));
return elements; return elements;
} }
@ -69,7 +70,7 @@ public final class ProgramBasalUtil {
if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT) { if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT) {
numberOfSlotsInCurrentElement++; numberOfSlotsInCurrentElement++;
} else { } else {
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false)); elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots]; previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1; numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false; extraAlternatePulse = false;
@ -96,7 +97,7 @@ public final class ProgramBasalUtil {
} else { } else {
// End of alternate pulse segment (no slots left in element) // End of alternate pulse segment (no slots left in element)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true)); elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots]; previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1; numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false; extraAlternatePulse = false;
@ -105,7 +106,7 @@ public final class ProgramBasalUtil {
} else { } else {
// End of alternate pulse segment (new number of pulses per slot) // End of alternate pulse segment (new number of pulses per slot)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true)); elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots]; previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1; numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false; extraAlternatePulse = false;
@ -115,7 +116,7 @@ public final class ProgramBasalUtil {
} }
} else if (previousPulsesPerSlot != pulsesPerSlot[currentTotalNumberOfSlots]) { } else if (previousPulsesPerSlot != pulsesPerSlot[currentTotalNumberOfSlots]) {
// End of segment (new number of pulses per slot) // End of segment (new number of pulses per slot)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false)); elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots]; previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
currentTotalNumberOfSlots++; currentTotalNumberOfSlots++;
@ -126,7 +127,7 @@ public final class ProgramBasalUtil {
} }
} }
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse)); elements.add(new BasalShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
return elements; return elements;
} }
@ -181,7 +182,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<LongInsulinProgramElement> elements, Date currentTime) { public static CurrentLongInsulinProgramElement calculateCurrentLongInsulinProgramElement(List<BasalInsulinProgramElement> elements, Date currentTime) {
Calendar instance = Calendar.getInstance(); Calendar instance = Calendar.getInstance();
instance.setTime(currentTime); instance.setTime(currentTime);
@ -193,7 +194,7 @@ public final class ProgramBasalUtil {
int startSlotIndex = 0; int startSlotIndex = 0;
byte index = 0; byte index = 0;
for (LongInsulinProgramElement element : elements) { for (BasalInsulinProgramElement element : elements) {
int startTimeInSeconds = startSlotIndex * 1_800; int startTimeInSeconds = startSlotIndex * 1_800;
int endTimeInSeconds = startTimeInSeconds + element.getNumberOfSlots() * 1_800; int endTimeInSeconds = startTimeInSeconds + element.getNumberOfSlots() * 1_800;
@ -236,12 +237,6 @@ public final class ProgramBasalUtil {
buffer.putShort(pulses); buffer.putShort(pulses);
} }
byte[] bytes = buffer.array(); return MessageUtil.createCheckSum(buffer.array());
short sum = 0;
for (byte b : bytes) {
sum += (short) (b & 0xff);
}
return sum;
} }
} }

View file

@ -1,36 +1,6 @@
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;
import java.nio.ByteBuffer;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable;
public class ShortInsulinProgramElement implements Encodable { public interface ShortInsulinProgramElement extends Encodable {
private final byte numberOfSlots; // 4 bits
private final short pulsesPerSlot; // 10 bits
private final boolean extraAlternatePulse;
public ShortInsulinProgramElement(byte numberOfSlots, short pulsesPerSlot, boolean extraAlternatePulse) {
this.numberOfSlots = numberOfSlots;
this.pulsesPerSlot = pulsesPerSlot;
this.extraAlternatePulse = extraAlternatePulse;
}
@Override public byte[] getEncoded() {
byte firstByte = (byte) ((((numberOfSlots - 1) & 0x0f) << 4) //
| ((extraAlternatePulse ? 1 : 0) << 3) //
| ((pulsesPerSlot >>> 8) & 0x03));
return ByteBuffer.allocate(2) //
.put(firstByte) //
.put((byte) (pulsesPerSlot & 0xff)) //
.array();
}
@Override public String toString() {
return "ShortInsulinProgramElement{" +
"numberOfSlotsMinusOne=" + numberOfSlots +
", pulsesPerSlot=" + pulsesPerSlot +
", extraAlternatePulse=" + extraAlternatePulse +
'}';
}
} }

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util; package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util;
public class CrcUtil { public class MessageUtil {
private static final short[] crc16table = {0, -32763, -32753, 10, -32741, 30, 20, -32751, -32717, 54, 60, -32711, 40, -32723, -32729, 34, -32669, 102, 108, -32663, 120, -32643, -32649, 114, 80, -32683, -32673, 90, -32693, 78, 68, -32703, -32573, 198, 204, -32567, 216, -32547, -32553, 210, 240, -32523, -32513, 250, -32533, 238, 228, -32543, 160, -32603, -32593, 170, -32581, 190, 180, -32591, -32621, 150, 156, -32615, 136, -32627, -32633, 130, -32381, 390, 396, -32375, 408, -32355, -32361, 402, 432, -32331, -32321, 442, -32341, 430, 420, -32351, 480, -32283, -32273, 490, -32261, 510, 500, -32271, -32301, 470, 476, -32295, 456, -32307, -32313, 450, 320, -32443, -32433, 330, -32421, 350, 340, -32431, -32397, 374, 380, -32391, 360, -32403, -32409, 354, -32477, 294, 300, -32471, 312, -32451, -32457, 306, 272, -32491, -32481, 282, -32501, 270, 260, -32511, -31997, 774, 780, -31991, 792, -31971, -31977, 786, 816, -31947, -31937, 826, -31957, 814, 804, -31967, 864, -31899, -31889, 874, -31877, 894, 884, -31887, -31917, 854, 860, -31911, 840, -31923, -31929, 834, 960, -31803, -31793, 970, -31781, 990, 980, -31791, -31757, 1014, 1020, -31751, 1000, -31763, -31769, 994, -31837, 934, 940, -31831, 952, -31811, -31817, 946, 912, -31851, -31841, 922, -31861, 910, 900, -31871, 640, -32123, -32113, 650, -32101, 670, 660, -32111, -32077, 694, 700, -32071, 680, -32083, -32089, 674, -32029, 742, 748, -32023, 760, -32003, -32009, 754, 720, -32043, -32033, 730, -32053, 718, 708, -32063, -32189, 582, 588, -32183, 600, -32163, -32169, 594, 624, -32139, -32129, 634, -32149, 622, 612, -32159, 544, -32219, -32209, 554, -32197, 574, 564, -32207, -32237, 534, 540, -32231, 520, -32243, -32249, 514}; private static final short[] crc16table = {0, -32763, -32753, 10, -32741, 30, 20, -32751, -32717, 54, 60, -32711, 40, -32723, -32729, 34, -32669, 102, 108, -32663, 120, -32643, -32649, 114, 80, -32683, -32673, 90, -32693, 78, 68, -32703, -32573, 198, 204, -32567, 216, -32547, -32553, 210, 240, -32523, -32513, 250, -32533, 238, 228, -32543, 160, -32603, -32593, 170, -32581, 190, 180, -32591, -32621, 150, 156, -32615, 136, -32627, -32633, 130, -32381, 390, 396, -32375, 408, -32355, -32361, 402, 432, -32331, -32321, 442, -32341, 430, 420, -32351, 480, -32283, -32273, 490, -32261, 510, 500, -32271, -32301, 470, 476, -32295, 456, -32307, -32313, 450, 320, -32443, -32433, 330, -32421, 350, 340, -32431, -32397, 374, 380, -32391, 360, -32403, -32409, 354, -32477, 294, 300, -32471, 312, -32451, -32457, 306, 272, -32491, -32481, 282, -32501, 270, 260, -32511, -31997, 774, 780, -31991, 792, -31971, -31977, 786, 816, -31947, -31937, 826, -31957, 814, 804, -31967, 864, -31899, -31889, 874, -31877, 894, 884, -31887, -31917, 854, 860, -31911, 840, -31923, -31929, 834, 960, -31803, -31793, 970, -31781, 990, 980, -31791, -31757, 1014, 1020, -31751, 1000, -31763, -31769, 994, -31837, 934, 940, -31831, 952, -31811, -31817, 946, 912, -31851, -31841, 922, -31861, 910, 900, -31871, 640, -32123, -32113, 650, -32101, 670, 660, -32111, -32077, 694, 700, -32071, 680, -32083, -32089, 674, -32029, 742, 748, -32023, 760, -32003, -32009, 754, 720, -32043, -32033, 730, -32053, 718, 708, -32063, -32189, 582, 588, -32183, 600, -32163, -32169, 594, 624, -32139, -32129, 634, -32149, 622, 612, -32159, 544, -32219, -32209, 554, -32197, 574, 564, -32207, -32237, 534, 540, -32231, 520, -32243, -32249, 514};
@ -29,4 +29,12 @@ public class CrcUtil {
} }
return s; return s;
} }
public static short createCheckSum(byte[] bytes) {
short sum = 0;
for (byte b : bytes) {
sum += (short) (b & 0xff);
}
return sum;
}
} }

View file

@ -0,0 +1,27 @@
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;
public class ProgramBolusCommandTest {
@Test
public void testProgramBolusCommand() throws DecoderException {
byte[] encoded = new ProgramBolusCommand.Builder() //
.setNumberOfUnits(5) //
.setProgramReminder(new ProgramReminder(false, true, (byte) 0)) //
.setDelayBetweenPulsesInEighthSeconds((byte) 16) //
.setUniqueId(37879809) //
.setSequenceNumber((short) 14) //
.setNonce(1229869870) //
.build() //
.getEncoded();
assertArrayEquals(Hex.decodeHex("02420001381F1A0E494E532E02010F01064000640064170D4003E800030D4000000000000080F6"), encoded);
}
}