WIP on Omnipod Dash program basal command

This commit is contained in:
Bart Sopers 2021-02-17 12:38:16 +01:00
parent c183a68798
commit 1c490f72ae
14 changed files with 430 additions and 210 deletions

View file

@ -10,7 +10,7 @@ public final class DeactivateCommand extends NonceEnabledCommand {
private static final short LENGTH = 6; private static final short LENGTH = 6;
private static final byte BODY_LENGTH = 4; private static final byte BODY_LENGTH = 4;
private DeactivateCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce) { DeactivateCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce) {
super(CommandType.DEACTIVATE, address, sequenceNumber, multiCommandFlag, nonce); super(CommandType.DEACTIVATE, address, sequenceNumber, multiCommandFlag, nonce);
} }

View file

@ -12,7 +12,7 @@ public final class GetVersionCommand extends HeaderEnabledCommand {
private static final short LENGTH = 6; private static final short LENGTH = 6;
private static final byte BODY_LENGTH = 4; private static final byte BODY_LENGTH = 4;
private GetVersionCommand(int address, short sequenceNumber, boolean multiCommandFlag) { GetVersionCommand(int address, short sequenceNumber, boolean multiCommandFlag) {
super(CommandType.GET_VERSION, address, sequenceNumber, multiCommandFlag); super(CommandType.GET_VERSION, address, sequenceNumber, multiCommandFlag);
} }

View file

@ -12,7 +12,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio
public final class ProgramAlertsCommand extends NonceEnabledCommand { public final class ProgramAlertsCommand extends NonceEnabledCommand {
private final List<AlertConfiguration> alertConfigurations; private final List<AlertConfiguration> alertConfigurations;
private ProgramAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, List<AlertConfiguration> alertConfigurations, int nonce) { ProgramAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, List<AlertConfiguration> alertConfigurations, int nonce) {
super(CommandType.PROGRAM_ALERTS, address, sequenceNumber, multiCommandFlag, nonce); super(CommandType.PROGRAM_ALERTS, address, sequenceNumber, multiCommandFlag, nonce);
this.alertConfigurations = new ArrayList<>(alertConfigurations); this.alertConfigurations = new ArrayList<>(alertConfigurations);
} }

View file

@ -2,23 +2,32 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command;
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.builder.CommandBuilder; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable; 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.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.ShortInsulinProgramElement;
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;
// Always preceded by 0x1a ProgramInsulinCommand // Always preceded by 0x1a ProgramInsulinCommand
public final class ProgramBasalCommand implements Command { public final class ProgramBasalCommand extends HeaderEnabledCommand {
private final List<InsulinProgramElement> insulinProgramElements; private final ProgramInsulinCommand interlockCommand;
private final List<LongInsulinProgramElement> 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;
private ProgramBasalCommand(List<InsulinProgramElement> insulinProgramElements, ProgramReminder programReminder, byte currentInsulinProgramElementIndex, short remainingTenthPulsesInCurrentInsulinProgramElement, int delayUntilNextTenthPulseInUsec) { ProgramBasalCommand(ProgramInsulinCommand interlockCommand, int address, short sequenceNumber, boolean multiCommandFlag, List<LongInsulinProgramElement> insulinProgramElements, ProgramReminder programReminder, byte currentInsulinProgramElementIndex, short remainingTenthPulsesInCurrentInsulinProgramElement, int delayUntilNextTenthPulseInUsec) {
super(CommandType.PROGRAM_BASAL, address, sequenceNumber, multiCommandFlag);
this.interlockCommand = interlockCommand;
this.insulinProgramElements = new ArrayList<>(insulinProgramElements); this.insulinProgramElements = new ArrayList<>(insulinProgramElements);
this.programReminder = programReminder; this.programReminder = programReminder;
this.currentInsulinProgramElementIndex = currentInsulinProgramElementIndex; this.currentInsulinProgramElementIndex = currentInsulinProgramElementIndex;
@ -42,55 +51,46 @@ public final class ProgramBasalCommand implements Command {
.put(currentInsulinProgramElementIndex) // .put(currentInsulinProgramElementIndex) //
.putShort(remainingTenthPulsesInCurrentInsulinProgramElement) // .putShort(remainingTenthPulsesInCurrentInsulinProgramElement) //
.putInt(delayUntilNextTenthPulseInUsec); .putInt(delayUntilNextTenthPulseInUsec);
for (InsulinProgramElement insulinProgramElement : insulinProgramElements) { for (LongInsulinProgramElement insulinProgramElement : insulinProgramElements) {
buffer.put(insulinProgramElement.getEncoded()); buffer.put(insulinProgramElement.getEncoded());
} }
return buffer.array();
}
@Override public CommandType getCommandType() { byte[] bolusCommand = buffer.array();
return CommandType.PROGRAM_BASAL; byte[] interlockCommand = this.interlockCommand.getEncoded();
byte[] header = encodeHeader(address, sequenceNumber, (short) (bolusCommand.length + interlockCommand.length), multiCommandFlag);
return ByteBuffer.allocate(bolusCommand.length + interlockCommand.length + header.length) //
.put(header) //
.put(interlockCommand) //
.put(bolusCommand) //
.array();
} }
@Override public String toString() { @Override public String toString() {
return "ProgramBasalCommand{" + return "ProgramBasalCommand{" +
"uniqueInsulinProgramElements=" + insulinProgramElements + "interlockCommand=" + interlockCommand +
", insulinProgramElements=" + insulinProgramElements +
", programReminder=" + programReminder + ", programReminder=" + programReminder +
", currentInsulinProgramElementIndex=" + currentInsulinProgramElementIndex + ", currentInsulinProgramElementIndex=" + currentInsulinProgramElementIndex +
", remainingTenthPulsesInCurrentInsulinProgramElement=" + remainingTenthPulsesInCurrentInsulinProgramElement + ", remainingTenthPulsesInCurrentInsulinProgramElement=" + remainingTenthPulsesInCurrentInsulinProgramElement +
", delayUntilNextTenthPulseInUsec=" + delayUntilNextTenthPulseInUsec + ", delayUntilNextTenthPulseInUsec=" + delayUntilNextTenthPulseInUsec +
", commandType=" + commandType +
", address=" + address +
", sequenceNumber=" + sequenceNumber +
", multiCommandFlag=" + multiCommandFlag +
'}'; '}';
} }
public static class InsulinProgramElement implements Encodable { public static final class Builder extends NonceEnabledCommandBuilder<Builder, ProgramBasalCommand> {
private final short totalTenthPulses; private BasalProgram basalProgram;
private final int delayBetweenTenthPulses;
public InsulinProgramElement(byte totalTenthPulses, short delayBetweenTenthPulses) {
this.totalTenthPulses = totalTenthPulses;
this.delayBetweenTenthPulses = delayBetweenTenthPulses;
}
@Override public byte[] getEncoded() {
return ByteBuffer.allocate(6) //
.putShort(totalTenthPulses) //
.putInt(delayBetweenTenthPulses) //
.array();
}
}
public static final class Builder implements CommandBuilder<ProgramBasalCommand> {
private List<InsulinProgramElement> insulinProgramElements;
private ProgramReminder programReminder; private ProgramReminder programReminder;
private Byte currentInsulinProgramElementIndex; private Date currentTime;
private Short remainingTenthPulsesInCurrentInsulinProgramElement;
private Integer delayUntilNextTenthPulseInUsec;
public Builder setInsulinProgramElements(List<InsulinProgramElement> insulinProgramElements) { public Builder setBasalProgram(BasalProgram basalProgram) {
if (insulinProgramElements == null) { if (basalProgram == null) {
throw new IllegalArgumentException("insulinProgramElements can not be null"); throw new IllegalArgumentException("basalProgram can not be null");
} }
this.insulinProgramElements = new ArrayList<>(insulinProgramElements); this.basalProgram = basalProgram;
return this; return this;
} }
@ -99,38 +99,36 @@ public final class ProgramBasalCommand implements Command {
return this; return this;
} }
public Builder setCurrentInsulinProgramElementIndex(Byte currentInsulinProgramElementIndex) { public Builder setCurrentTime(Date currentTime) {
this.currentInsulinProgramElementIndex = currentInsulinProgramElementIndex; this.currentTime = currentTime;
return this; return this;
} }
public Builder setRemainingTenthPulsesInCurrentInsulinProgramElement(Short remainingTenthPulsesInCurrentInsulinProgramElement) { @Override protected ProgramBasalCommand buildCommand() {
this.remainingTenthPulsesInCurrentInsulinProgramElement = remainingTenthPulsesInCurrentInsulinProgramElement; if (basalProgram == null) {
return this; throw new IllegalArgumentException("basalProgram can not be null");
}
public Builder setDelayUntilNextTenthPulseInUsec(Integer delayUntilNextTenthPulseInUsec) {
this.delayUntilNextTenthPulseInUsec = delayUntilNextTenthPulseInUsec;
return this;
}
@Override public ProgramBasalCommand build() {
if (insulinProgramElements == null) {
throw new IllegalArgumentException("insulinProgramElements can not be null");
} }
if (programReminder == null) { if (programReminder == null) {
throw new IllegalArgumentException("programReminder can not be null"); throw new IllegalArgumentException("programReminder can not be null");
} }
if (currentInsulinProgramElementIndex == null) { if (currentTime == null) {
throw new IllegalArgumentException("currentInsulinProgramElementIndex can not be null"); throw new IllegalArgumentException("currentTime can not be null");
} }
if (remainingTenthPulsesInCurrentInsulinProgramElement == null) {
throw new IllegalArgumentException("remainingTenthPulsesInCurrentInsulinProgramElement can not be null"); short[] pulsesPerSlot = ProgramBasalUtil.mapBasalProgramToPulsesPerSlot(basalProgram);
} CurrentSlot currentSlot = ProgramBasalUtil.calculateCurrentSlot(pulsesPerSlot, currentTime);
if (delayUntilNextTenthPulseInUsec == null) { List<LongInsulinProgramElement> longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(pulsesPerSlot);
throw new IllegalArgumentException("delayUntilNextTenthPulseInUsec can not be null"); List<ShortInsulinProgramElement> shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot);
} short checksum = ProgramBasalUtil.createChecksum();
return new ProgramBasalCommand(insulinProgramElements, programReminder, currentInsulinProgramElementIndex, remainingTenthPulsesInCurrentInsulinProgramElement, delayUntilNextTenthPulseInUsec); byte currentInsulinProgramElementIndex = 0; // TODO
short remainingTenthPulsesInCurrentInsulinProgramElement = 0; // TODO
int delayUntilNextPulseInUsec = 0; // TODO
ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(address, sequenceNumber, multiCommandFlag, nonce,
shortInsulinProgramElements, currentSlot.getIndex(), checksum, (short) (currentSlot.getSecondsRemaining() * 8),
currentSlot.getPulsesRemaining(), ProgramInsulinCommand.DeliveryType.BASAL);
return new ProgramBasalCommand(interlockCommand, address, sequenceNumber, multiCommandFlag, longInsulinProgramElements, programReminder, currentInsulinProgramElementIndex, remainingTenthPulsesInCurrentInsulinProgramElement, delayUntilNextPulseInUsec);
} }
} }
} }

View file

@ -2,153 +2,58 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command;
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.NonceEnabledCommand; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.NonceEnabledCommand;
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.ShortInsulinProgramElement;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable;
// Always followed by one of: 0x13, 0x16, 0x17 // Always followed by one of: 0x13, 0x16, 0x17
public final class ProgramInsulinCommand extends NonceEnabledCommand { final class ProgramInsulinCommand extends NonceEnabledCommand {
private final List<InsulinProgramElement> insulinProgramElements; private final List<ShortInsulinProgramElement> insulinProgramElements;
private final byte currentHalfHourEntryIndex; private final byte currentSlot;
private final short checksum; private final short checksum;
private final short remainingEighthSecondsInCurrentHalfHourEntry; private final short remainingEighthSecondsInCurrentSlot;
private final short remainingPulsesInCurrentHalfHourEntry; private final short remainingPulsesInCurrentSlot;
private final DeliveryType deliveryType; private final DeliveryType deliveryType;
private final Command interlockCommand;
private static final List<CommandType> ALLOWED_INTERLOCK_COMMANDS = Arrays.asList( ProgramInsulinCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce, List<ShortInsulinProgramElement> insulinProgramElements, byte currentSlot, short checksum, short remainingEighthSecondsInCurrentSlot, short remainingPulsesInCurrentSlot, DeliveryType deliveryType) {
CommandType.PROGRAM_BASAL,
CommandType.PROGRAM_TEMP_BASAL,
CommandType.PROGRAM_BOLUS
);
private ProgramInsulinCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce, List<InsulinProgramElement> insulinProgramElements, byte currentHalfHourEntryIndex, short checksum, short remainingEighthSecondsInCurrentHalfHourEntry, short remainingPulsesInCurrentHalfHourEntry, DeliveryType deliveryType, Command interlockCommand) {
super(CommandType.PROGRAM_INSULIN, address, sequenceNumber, multiCommandFlag, nonce); super(CommandType.PROGRAM_INSULIN, address, sequenceNumber, multiCommandFlag, nonce);
this.insulinProgramElements = new ArrayList<>(insulinProgramElements); this.insulinProgramElements = new ArrayList<>(insulinProgramElements);
this.currentHalfHourEntryIndex = currentHalfHourEntryIndex; this.currentSlot = currentSlot;
this.checksum = checksum; this.checksum = checksum;
this.remainingEighthSecondsInCurrentHalfHourEntry = remainingEighthSecondsInCurrentHalfHourEntry; this.remainingEighthSecondsInCurrentSlot = remainingEighthSecondsInCurrentSlot;
this.remainingPulsesInCurrentHalfHourEntry = remainingPulsesInCurrentHalfHourEntry; this.remainingPulsesInCurrentSlot = remainingPulsesInCurrentSlot;
this.deliveryType = deliveryType; this.deliveryType = deliveryType;
this.interlockCommand = interlockCommand;
} }
public short getLength() { short getLength() {
return (short) (insulinProgramElements.size() * 6 + 10); return (short) (insulinProgramElements.size() * 6 + 10);
} }
public byte getBodyLength() { byte getBodyLength() {
return (byte) (insulinProgramElements.size() * 6 + 8); return (byte) (insulinProgramElements.size() * 6 + 8);
} }
@Override public byte[] getEncoded() { @Override public byte[] getEncoded() {
ByteBuffer commandBuffer = ByteBuffer.allocate(this.getLength()) // ByteBuffer buffer = ByteBuffer.allocate(this.getLength()) //
.put(commandType.getValue()) // .put(commandType.getValue()) //
.put(getBodyLength()) // .put(getBodyLength()) //
.putInt(nonce) // .putInt(nonce) //
.put(deliveryType.getValue()) // .put(deliveryType.getValue()) //
.putShort(checksum) // .putShort(checksum) //
.put(currentHalfHourEntryIndex) // .put(currentSlot) //
.putShort(remainingEighthSecondsInCurrentHalfHourEntry) // .putShort(remainingEighthSecondsInCurrentSlot) //
.putShort(remainingPulsesInCurrentHalfHourEntry); .putShort(remainingPulsesInCurrentSlot);
for (InsulinProgramElement element : insulinProgramElements) { for (ShortInsulinProgramElement element : insulinProgramElements) {
commandBuffer.put(element.getEncoded()); buffer.put(element.getEncoded());
} }
byte[] command = commandBuffer.array(); return buffer.array();
byte[] interlock = interlockCommand.getEncoded();
short totalLength = (short) (command.length + interlock.length + HEADER_LENGTH);
return ByteBuffer.allocate(totalLength) //
.put(encodeHeader(address, sequenceNumber, totalLength, multiCommandFlag)) //
.put(command) //
.put(interlock) //
.array();
} }
public static final class Builder extends NonceEnabledCommandBuilder<Builder, ProgramInsulinCommand> { enum DeliveryType {
private List<InsulinProgramElement> insulinProgramElements;
private Byte currentHalfOurEntryIndex;
private Short checksum;
private Short remainingEighthSecondsInCurrentHalfHourEntry;
private Short remainingPulsesInCurrentHalfHourEntry;
private DeliveryType deliveryType;
private Command interlockCommand;
public Builder setInsulinProgramElements(List<InsulinProgramElement> insulinProgramElements) {
if (insulinProgramElements == null) {
throw new IllegalArgumentException("insulinProgramElements can not be null");
}
this.insulinProgramElements = new ArrayList<>(insulinProgramElements);
return this;
}
public Builder setCurrentHalfOurEntryIndex(byte currentHalfOurEntryIndex) {
this.currentHalfOurEntryIndex = currentHalfOurEntryIndex;
return this;
}
public Builder setChecksum(short checksum) {
this.checksum = checksum;
return this;
}
public Builder setRemainingEighthSecondsInCurrentHalfHourEntryIndex(short remainingEighthSecondsInCurrentHalfHourEntry) {
this.remainingEighthSecondsInCurrentHalfHourEntry = remainingEighthSecondsInCurrentHalfHourEntry;
return this;
}
public Builder setRemainingPulsesInCurrentHalfHourEntry(short remainingPulsesInCurrentHalfHourEntry) {
this.remainingPulsesInCurrentHalfHourEntry = remainingPulsesInCurrentHalfHourEntry;
return this;
}
public Builder setDeliveryType(DeliveryType deliveryType) {
this.deliveryType = deliveryType;
return this;
}
public Builder setInterlockCommand(Command interlockCommand) {
if (!ALLOWED_INTERLOCK_COMMANDS.contains(interlockCommand.getCommandType())) {
throw new IllegalArgumentException("Illegal interlock command type");
}
this.interlockCommand = interlockCommand;
return this;
}
@Override protected final ProgramInsulinCommand buildCommand() {
if (insulinProgramElements == null) {
throw new IllegalArgumentException("insulinProgramElements can not be null");
}
if (currentHalfOurEntryIndex == null) {
throw new IllegalArgumentException("currentHalfOurEntryIndex can not be null");
}
if (checksum == null) {
throw new IllegalArgumentException("checksum can not be null");
}
if (remainingEighthSecondsInCurrentHalfHourEntry == null) {
throw new IllegalArgumentException("remainingEighthSecondsInCurrentHalfHourEntry can not be null");
}
if (remainingPulsesInCurrentHalfHourEntry == null) {
throw new IllegalArgumentException("remainingPulsesInCurrentHalfHourEntry can not be null");
}
if (deliveryType == null) {
throw new IllegalArgumentException("deliveryType can not be null");
}
if (interlockCommand == null) {
throw new IllegalArgumentException("interlockCommand can not be null");
}
return new ProgramInsulinCommand(address, sequenceNumber, multiCommandFlag, nonce, insulinProgramElements, currentHalfOurEntryIndex, checksum, remainingEighthSecondsInCurrentHalfHourEntry, remainingPulsesInCurrentHalfHourEntry, deliveryType, interlockCommand);
}
}
public enum DeliveryType {
BASAL((byte) 0x00), BASAL((byte) 0x00),
TEMP_BASAL((byte) 0x01), TEMP_BASAL((byte) 0x01),
BOLUS((byte) 0x02); BOLUS((byte) 0x02);
@ -167,12 +72,11 @@ public final class ProgramInsulinCommand extends NonceEnabledCommand {
@Override public String toString() { @Override public String toString() {
return "ProgramInsulinCommand{" + return "ProgramInsulinCommand{" +
"insulinProgramElements=" + insulinProgramElements + "insulinProgramElements=" + insulinProgramElements +
", currentHalfHourEntryIndex=" + currentHalfHourEntryIndex + ", currentSlot=" + currentSlot +
", checksum=" + checksum + ", checksum=" + checksum +
", remainingEighthSecondsInCurrentHalfHourEntry=" + remainingEighthSecondsInCurrentHalfHourEntry + ", remainingEighthSecondsInCurrentSlot=" + remainingEighthSecondsInCurrentSlot +
", remainingPulsesInCurrentHalfHourEntry=" + remainingPulsesInCurrentHalfHourEntry + ", remainingPulsesInCurrentSlot=" + remainingPulsesInCurrentSlot +
", deliveryType=" + deliveryType + ", deliveryType=" + deliveryType +
", interlockCommand=" + interlockCommand +
", nonce=" + nonce + ", nonce=" + nonce +
", commandType=" + commandType + ", commandType=" + commandType +
", address=" + address + ", address=" + address +
@ -181,26 +85,4 @@ public final class ProgramInsulinCommand extends NonceEnabledCommand {
'}'; '}';
} }
public static class InsulinProgramElement implements Encodable {
private final byte numberOfHalfOurEntries; // 4 bits
private final short numberOfPulsesPerHalfOurEntry; // 10 bits
private final boolean extraAlternatePulse;
public InsulinProgramElement(byte numberOfHalfOurEntries, short numberOfPulsesPerHalfOurEntry, boolean extraAlternatePulse) {
this.numberOfHalfOurEntries = numberOfHalfOurEntries;
this.numberOfPulsesPerHalfOurEntry = numberOfPulsesPerHalfOurEntry;
this.extraAlternatePulse = extraAlternatePulse;
}
@Override public byte[] getEncoded() {
byte firstByte = (byte) ((((numberOfHalfOurEntries - 1) & 0x0f) << 4) //
| ((extraAlternatePulse ? 1 : 0) << 3) //
| ((numberOfPulsesPerHalfOurEntry >>> 8) & 0x03));
return ByteBuffer.allocate(2) //
.put(firstByte) //
.put((byte) (numberOfPulsesPerHalfOurEntry & 0xff)) //
.array();
}
}
} }

View file

@ -17,7 +17,7 @@ public final class SetUniqueIdCommand extends HeaderEnabledCommand {
private final int podSequenceNumber; private final int podSequenceNumber;
private final Date initializationTime; private final Date initializationTime;
private SetUniqueIdCommand(int address, short sequenceNumber, boolean multiCommandFlag, int lotNumber, int podSequenceNumber, Date initializationTime) { SetUniqueIdCommand(int address, short sequenceNumber, boolean multiCommandFlag, int lotNumber, int podSequenceNumber, Date initializationTime) {
super(CommandType.SET_UNIQUE_ID, address, sequenceNumber, multiCommandFlag); super(CommandType.SET_UNIQUE_ID, address, sequenceNumber, multiCommandFlag);
this.lotNumber = lotNumber; this.lotNumber = lotNumber;
this.podSequenceNumber = podSequenceNumber; this.podSequenceNumber = podSequenceNumber;

View file

@ -14,7 +14,7 @@ public final class SilenceAlertsCommand extends NonceEnabledCommand {
private final SilenceAlertCommandParameters parameters; private final SilenceAlertCommandParameters parameters;
private SilenceAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, SilenceAlertCommandParameters parameters, int nonce) { SilenceAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, SilenceAlertCommandParameters parameters, int nonce) {
super(CommandType.SILENCE_ALERTS, address, sequenceNumber, multiCommandFlag, nonce); super(CommandType.SILENCE_ALERTS, address, sequenceNumber, multiCommandFlag, nonce);
this.parameters = parameters; this.parameters = parameters;
} }

View file

@ -16,7 +16,7 @@ public final class StopDeliveryCommand extends NonceEnabledCommand {
private final DeliveryType deliveryType; private final DeliveryType deliveryType;
private final BeepType beepType; private final BeepType beepType;
private StopDeliveryCommand(int address, short sequenceNumber, boolean multiCommandFlag, DeliveryType deliveryType, BeepType beepType, int nonce) { StopDeliveryCommand(int address, short sequenceNumber, boolean multiCommandFlag, DeliveryType deliveryType, BeepType beepType, int nonce) {
super(CommandType.STOP_DELIVERY, address, sequenceNumber, multiCommandFlag, nonce); super(CommandType.STOP_DELIVERY, address, sequenceNumber, multiCommandFlag, nonce);
this.deliveryType = deliveryType; this.deliveryType = deliveryType;
this.beepType = beepType; this.beepType = beepType;

View file

@ -0,0 +1,33 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
public class CurrentSlot {
private byte index;
private short secondsRemaining;
private short pulsesRemaining;
public CurrentSlot(byte index, short secondsRemaining, short pulsesRemaining) {
this.index = index;
this.secondsRemaining = secondsRemaining;
this.pulsesRemaining = pulsesRemaining;
}
public byte getIndex() {
return index;
}
public short getSecondsRemaining() {
return secondsRemaining;
}
public short getPulsesRemaining() {
return pulsesRemaining;
}
@Override public String toString() {
return "CurrentSlot{" +
"index=" + index +
", secondsRemaining=" + secondsRemaining +
", pulsesRemaining=" + pulsesRemaining +
'}';
}
}

View file

@ -0,0 +1,29 @@
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;
public class LongInsulinProgramElement implements Encodable {
private final short totalTenthPulses;
private final int delayBetweenTenthPulses;
public LongInsulinProgramElement(byte totalTenthPulses, short delayBetweenTenthPulses) {
this.totalTenthPulses = totalTenthPulses;
this.delayBetweenTenthPulses = delayBetweenTenthPulses;
}
@Override public byte[] getEncoded() {
return ByteBuffer.allocate(6) //
.putShort(totalTenthPulses) //
.putInt(delayBetweenTenthPulses) //
.array();
}
@Override public String toString() {
return "LongInsulinProgramElement{" +
"totalTenthPulses=" + totalTenthPulses +
", delayBetweenTenthPulses=" + delayBetweenTenthPulses +
'}';
}
}

View file

@ -0,0 +1,150 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram;
public final class ProgramBasalUtil {
private static final byte NUMBER_OF_BASAL_SLOTS = 48;
private static final byte MAX_NUMBER_OF_SLOTS_IN_SHORT_INSULIN_PROGRAM_ELEMENT = 16;
private ProgramBasalUtil() {
}
public static List<LongInsulinProgramElement> mapPulsesPerSlotToLongInsulinProgramElements(short[] pulsesPerSlot) {
if (pulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) {
throw new IllegalArgumentException("Basal program must contain 48 slots");
}
// TODO
return new ArrayList<>();
}
public static List<ShortInsulinProgramElement> mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) {
if (pulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) {
throw new IllegalArgumentException("Basal program must contain 48 slots");
}
List<ShortInsulinProgramElement> elements = new ArrayList<>();
boolean extraAlternatePulse = false;
short previousPulsesPerSlot = 0;
byte numberOfSlotsInCurrentElement = 0;
byte currentTotalNumberOfSlots = 0;
while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) {
if (currentTotalNumberOfSlots == 0) {
// First slot
previousPulsesPerSlot = pulsesPerSlot[0];
currentTotalNumberOfSlots++;
numberOfSlotsInCurrentElement = 1;
} else if (pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot) {
// Subsequent slot in element (same pulses per slot as previous slot)
if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_SHORT_INSULIN_PROGRAM_ELEMENT) {
numberOfSlotsInCurrentElement++;
} else {
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
}
currentTotalNumberOfSlots++;
} else if (numberOfSlotsInCurrentElement == 1 && pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot + 1) {
// Second slot of segment with extra alternate pulse
boolean expectAlternatePulseForNextSegment = false;
currentTotalNumberOfSlots++;
extraAlternatePulse = true;
while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) {
// Loop rest alternate pulse segment
if (pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot + (expectAlternatePulseForNextSegment ? 1 : 0)) {
// Still in alternate pulse segment
currentTotalNumberOfSlots++;
expectAlternatePulseForNextSegment = !expectAlternatePulseForNextSegment;
if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_SHORT_INSULIN_PROGRAM_ELEMENT) {
numberOfSlotsInCurrentElement++;
} else {
// End of alternate pulse segment (no slots left in element)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
break;
}
} else {
// End of alternate pulse segment (new number of pulses per slot)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, true));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
numberOfSlotsInCurrentElement = 1;
extraAlternatePulse = false;
currentTotalNumberOfSlots++;
break;
}
}
} else if (previousPulsesPerSlot != pulsesPerSlot[currentTotalNumberOfSlots]) {
// End of segment (new number of pulses per slot)
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, false));
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots];
currentTotalNumberOfSlots++;
extraAlternatePulse = false;
numberOfSlotsInCurrentElement = 1;
} else {
throw new IllegalStateException("Reached illegal point in mapBasalProgramToShortInsulinProgramElements");
}
}
elements.add(new ShortInsulinProgramElement(numberOfSlotsInCurrentElement, previousPulsesPerSlot, extraAlternatePulse));
return elements;
}
public static short[] mapBasalProgramToPulsesPerSlot(BasalProgram basalProgram) {
short[] pulsesPerSlot = new short[NUMBER_OF_BASAL_SLOTS];
for (BasalProgram.Segment segment : basalProgram.getSegments()) {
boolean remainingPulse = false;
for (int i = segment.getStartSlotIndex(); i < segment.getEndSlotIndex(); i++) {
pulsesPerSlot[i] = (short) (segment.getPulsesPerHour() / 2);
if (segment.getPulsesPerHour() % 2 == 1) { // Do extra alternate pulse
if (remainingPulse) {
pulsesPerSlot[i] += 1;
}
remainingPulse = !remainingPulse;
}
}
}
return pulsesPerSlot;
}
public static CurrentSlot calculateCurrentSlot(short[] pulsesPerSlot, Date currentTime) {
Calendar instance = Calendar.getInstance();
instance.setTime(currentTime);
int hourOfDay = instance.get(Calendar.HOUR_OF_DAY);
int minuteOfHour = instance.get(Calendar.MINUTE);
int secondOfMinute = instance.get(Calendar.SECOND);
byte index = (byte) ((hourOfDay * 60 + minuteOfHour) / 30);
short secondsRemaining = (short) ((index + 1) * 1800 - (secondOfMinute + hourOfDay * 3600 + minuteOfHour * 60));
short pulsesRemaining = (short) ((double) pulsesPerSlot[index] * secondsRemaining / 1800);
return new CurrentSlot(index, secondsRemaining, pulsesRemaining);
}
public static short createChecksum() {
// TODO
return 0;
}
}

View file

@ -0,0 +1,36 @@
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;
public class ShortInsulinProgramElement implements Encodable {
private final byte numberOfSlotsMinusOne; // 4 bits
private final short pulsesPerSlot; // 10 bits
private final boolean extraAlternatePulse;
public ShortInsulinProgramElement(byte numberOfSlotsMinusOne, short pulsesPerSlot, boolean extraAlternatePulse) {
this.numberOfSlotsMinusOne = numberOfSlotsMinusOne;
this.pulsesPerSlot = pulsesPerSlot;
this.extraAlternatePulse = extraAlternatePulse;
}
@Override public byte[] getEncoded() {
byte firstByte = (byte) ((((numberOfSlotsMinusOne - 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=" + numberOfSlotsMinusOne +
", pulsesPerSlot=" + pulsesPerSlot +
", extraAlternatePulse=" + extraAlternatePulse +
'}';
}
}

View file

@ -0,0 +1,92 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BasalProgram {
private final List<Segment> segments;
public BasalProgram(List<Segment> segments) {
if (segments == null) {
throw new IllegalArgumentException("segments can not be null");
}
// TODO validate segments
this.segments = new ArrayList<>(segments);
}
public void addSegment(Segment segment) {
segments.add(segment);
}
public List<Segment> getSegments() {
return Collections.unmodifiableList(segments);
}
public boolean isZeroBasal() {
int total = 0;
for (Segment segment : segments) {
total += segment.getBasalRateInHundredthUnitsPerHour();
}
return total == 0;
}
public boolean hasZeroUnitSegments() {
for (Segment segment : segments) {
if (segment.getBasalRateInHundredthUnitsPerHour() == 0) {
return true;
}
}
return false;
}
public static class Segment {
private static final byte PULSES_PER_UNIT = 20;
private final short startSlotIndex;
private final short endSlotIndex;
private final int basalRateInHundredthUnitsPerHour;
public Segment(short startSlotIndex, short endSlotIndex, int basalRateInHundredthUnitsPerHour) {
this.startSlotIndex = startSlotIndex;
this.endSlotIndex = endSlotIndex;
this.basalRateInHundredthUnitsPerHour = basalRateInHundredthUnitsPerHour;
}
public short getStartSlotIndex() {
return startSlotIndex;
}
public short getEndSlotIndex() {
return endSlotIndex;
}
public int getBasalRateInHundredthUnitsPerHour() {
return basalRateInHundredthUnitsPerHour;
}
public short getPulsesPerHour() {
return (short) (basalRateInHundredthUnitsPerHour * PULSES_PER_UNIT / 100);
}
public short getNumberOfSlots() {
return (short) (endSlotIndex - startSlotIndex);
}
@Override public String toString() {
return "Segment{" +
"startSlotIndex=" + startSlotIndex +
", endSlotIndex=" + endSlotIndex +
", basalRateInHundredthUnitsPerHour=" + basalRateInHundredthUnitsPerHour +
'}';
}
}
@Override public String toString() {
return "BasalProgram{" +
"segments=" + segments +
'}';
}
}

View file

@ -16,8 +16,8 @@ public class NakResponseTest {
@Test @Test
public void testValidResponse() throws DecoderException { public void testValidResponse() throws DecoderException {
byte[] encoded = Hex.decodeHex("0603070009"); byte[] encoded = Hex.decodeHex("0603070009");
NakResponse response = new NakResponse(encoded); NakResponse response = new NakResponse(encoded);
assertArrayEquals(encoded, response.getEncoded()); assertArrayEquals(encoded, response.getEncoded());
assertNotSame(encoded, response.getEncoded()); assertNotSame(encoded, response.getEncoded());
assertEquals(ResponseType.NAK_RESPONSE, response.getResponseType()); assertEquals(ResponseType.NAK_RESPONSE, response.getResponseType());