From 1c490f72aeeffe08943ecfbd64890319724898fb Mon Sep 17 00:00:00 2001 From: Bart Sopers Date: Wed, 17 Feb 2021 12:38:16 +0100 Subject: [PATCH] WIP on Omnipod Dash program basal command --- .../driver/pod/command/DeactivateCommand.java | 2 +- .../driver/pod/command/GetVersionCommand.java | 2 +- .../pod/command/ProgramAlertsCommand.java | 2 +- .../pod/command/ProgramBasalCommand.java | 122 +++++++------ .../pod/command/ProgramInsulinCommand.java | 164 +++--------------- .../pod/command/SetUniqueIdCommand.java | 2 +- .../pod/command/SilenceAlertsCommand.java | 2 +- .../pod/command/StopDeliveryCommand.java | 2 +- .../command/insulin/program/CurrentSlot.java | 33 ++++ .../program/LongInsulinProgramElement.java | 29 ++++ .../insulin/program/ProgramBasalUtil.java | 150 ++++++++++++++++ .../program/ShortInsulinProgramElement.java | 36 ++++ .../driver/pod/definition/BasalProgram.java | 92 ++++++++++ .../driver/pod/response/NakResponseTest.java | 2 +- 14 files changed, 430 insertions(+), 210 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentSlot.java create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/LongInsulinProgramElement.java create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ProgramBasalUtil.java create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ShortInsulinProgramElement.java create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/BasalProgram.java diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/DeactivateCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/DeactivateCommand.java index 7f6fee2ce6..784e95b40b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/DeactivateCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/DeactivateCommand.java @@ -10,7 +10,7 @@ public final class DeactivateCommand extends NonceEnabledCommand { private static final short LENGTH = 6; 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); } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/GetVersionCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/GetVersionCommand.java index 6dc9f65f1e..e8cc704efa 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/GetVersionCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/GetVersionCommand.java @@ -12,7 +12,7 @@ public final class GetVersionCommand extends HeaderEnabledCommand { private static final short LENGTH = 6; 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); } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramAlertsCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramAlertsCommand.java index ec46411954..30150a086e 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramAlertsCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramAlertsCommand.java @@ -12,7 +12,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio public final class ProgramAlertsCommand extends NonceEnabledCommand { private final List alertConfigurations; - private ProgramAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, List alertConfigurations, int nonce) { + ProgramAlertsCommand(int address, short sequenceNumber, boolean multiCommandFlag, List alertConfigurations, int nonce) { super(CommandType.PROGRAM_ALERTS, address, sequenceNumber, multiCommandFlag, nonce); this.alertConfigurations = new ArrayList<>(alertConfigurations); } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBasalCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBasalCommand.java index 5060ffb138..f679adde88 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBasalCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBasalCommand.java @@ -2,23 +2,32 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Date; 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.builder.CommandBuilder; -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.Encodable; +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.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; // Always preceded by 0x1a ProgramInsulinCommand -public final class ProgramBasalCommand implements Command { - private final List insulinProgramElements; +public final class ProgramBasalCommand extends HeaderEnabledCommand { + private final ProgramInsulinCommand interlockCommand; + private final List insulinProgramElements; private final ProgramReminder programReminder; private final byte currentInsulinProgramElementIndex; private final short remainingTenthPulsesInCurrentInsulinProgramElement; private final int delayUntilNextTenthPulseInUsec; - private ProgramBasalCommand(List insulinProgramElements, ProgramReminder programReminder, byte currentInsulinProgramElementIndex, short remainingTenthPulsesInCurrentInsulinProgramElement, int delayUntilNextTenthPulseInUsec) { + ProgramBasalCommand(ProgramInsulinCommand interlockCommand, int address, short sequenceNumber, boolean multiCommandFlag, List 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.programReminder = programReminder; this.currentInsulinProgramElementIndex = currentInsulinProgramElementIndex; @@ -42,55 +51,46 @@ public final class ProgramBasalCommand implements Command { .put(currentInsulinProgramElementIndex) // .putShort(remainingTenthPulsesInCurrentInsulinProgramElement) // .putInt(delayUntilNextTenthPulseInUsec); - for (InsulinProgramElement insulinProgramElement : insulinProgramElements) { + for (LongInsulinProgramElement insulinProgramElement : insulinProgramElements) { buffer.put(insulinProgramElement.getEncoded()); } - return buffer.array(); - } - @Override public CommandType getCommandType() { - return CommandType.PROGRAM_BASAL; + byte[] bolusCommand = buffer.array(); + 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() { return "ProgramBasalCommand{" + - "uniqueInsulinProgramElements=" + insulinProgramElements + + "interlockCommand=" + interlockCommand + + ", insulinProgramElements=" + insulinProgramElements + ", programReminder=" + programReminder + ", currentInsulinProgramElementIndex=" + currentInsulinProgramElementIndex + ", remainingTenthPulsesInCurrentInsulinProgramElement=" + remainingTenthPulsesInCurrentInsulinProgramElement + ", delayUntilNextTenthPulseInUsec=" + delayUntilNextTenthPulseInUsec + + ", commandType=" + commandType + + ", address=" + address + + ", sequenceNumber=" + sequenceNumber + + ", multiCommandFlag=" + multiCommandFlag + '}'; } - public static class InsulinProgramElement implements Encodable { - private final short totalTenthPulses; - 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 { - private List insulinProgramElements; + public static final class Builder extends NonceEnabledCommandBuilder { + private BasalProgram basalProgram; private ProgramReminder programReminder; - private Byte currentInsulinProgramElementIndex; - private Short remainingTenthPulsesInCurrentInsulinProgramElement; - private Integer delayUntilNextTenthPulseInUsec; + private Date currentTime; - public Builder setInsulinProgramElements(List insulinProgramElements) { - if (insulinProgramElements == null) { - throw new IllegalArgumentException("insulinProgramElements can not be null"); + public Builder setBasalProgram(BasalProgram basalProgram) { + if (basalProgram == null) { + throw new IllegalArgumentException("basalProgram can not be null"); } - this.insulinProgramElements = new ArrayList<>(insulinProgramElements); + this.basalProgram = basalProgram; return this; } @@ -99,38 +99,36 @@ public final class ProgramBasalCommand implements Command { return this; } - public Builder setCurrentInsulinProgramElementIndex(Byte currentInsulinProgramElementIndex) { - this.currentInsulinProgramElementIndex = currentInsulinProgramElementIndex; + public Builder setCurrentTime(Date currentTime) { + this.currentTime = currentTime; return this; } - public Builder setRemainingTenthPulsesInCurrentInsulinProgramElement(Short remainingTenthPulsesInCurrentInsulinProgramElement) { - this.remainingTenthPulsesInCurrentInsulinProgramElement = remainingTenthPulsesInCurrentInsulinProgramElement; - return this; - } - - 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"); + @Override protected ProgramBasalCommand buildCommand() { + if (basalProgram == null) { + throw new IllegalArgumentException("basalProgram can not be null"); } if (programReminder == null) { throw new IllegalArgumentException("programReminder can not be null"); } - if (currentInsulinProgramElementIndex == null) { - throw new IllegalArgumentException("currentInsulinProgramElementIndex can not be null"); + if (currentTime == null) { + throw new IllegalArgumentException("currentTime can not be null"); } - if (remainingTenthPulsesInCurrentInsulinProgramElement == null) { - throw new IllegalArgumentException("remainingTenthPulsesInCurrentInsulinProgramElement can not be null"); - } - if (delayUntilNextTenthPulseInUsec == null) { - throw new IllegalArgumentException("delayUntilNextTenthPulseInUsec can not be null"); - } - return new ProgramBasalCommand(insulinProgramElements, programReminder, currentInsulinProgramElementIndex, remainingTenthPulsesInCurrentInsulinProgramElement, delayUntilNextTenthPulseInUsec); + + short[] pulsesPerSlot = ProgramBasalUtil.mapBasalProgramToPulsesPerSlot(basalProgram); + CurrentSlot currentSlot = ProgramBasalUtil.calculateCurrentSlot(pulsesPerSlot, currentTime); + List longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(pulsesPerSlot); + List shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); + short checksum = ProgramBasalUtil.createChecksum(); + 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); } } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramInsulinCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramInsulinCommand.java index 30e175d616..6046e17a57 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramInsulinCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramInsulinCommand.java @@ -2,153 +2,58 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; 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.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.definition.Encodable; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement; // Always followed by one of: 0x13, 0x16, 0x17 -public final class ProgramInsulinCommand extends NonceEnabledCommand { - private final List insulinProgramElements; - private final byte currentHalfHourEntryIndex; +final class ProgramInsulinCommand extends NonceEnabledCommand { + private final List insulinProgramElements; + private final byte currentSlot; private final short checksum; - private final short remainingEighthSecondsInCurrentHalfHourEntry; - private final short remainingPulsesInCurrentHalfHourEntry; + private final short remainingEighthSecondsInCurrentSlot; + private final short remainingPulsesInCurrentSlot; private final DeliveryType deliveryType; - private final Command interlockCommand; - private static final List ALLOWED_INTERLOCK_COMMANDS = Arrays.asList( - CommandType.PROGRAM_BASAL, - CommandType.PROGRAM_TEMP_BASAL, - CommandType.PROGRAM_BOLUS - ); - - private ProgramInsulinCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce, List insulinProgramElements, byte currentHalfHourEntryIndex, short checksum, short remainingEighthSecondsInCurrentHalfHourEntry, short remainingPulsesInCurrentHalfHourEntry, DeliveryType deliveryType, Command interlockCommand) { + ProgramInsulinCommand(int address, short sequenceNumber, boolean multiCommandFlag, int nonce, List insulinProgramElements, byte currentSlot, short checksum, short remainingEighthSecondsInCurrentSlot, short remainingPulsesInCurrentSlot, DeliveryType deliveryType) { super(CommandType.PROGRAM_INSULIN, address, sequenceNumber, multiCommandFlag, nonce); this.insulinProgramElements = new ArrayList<>(insulinProgramElements); - this.currentHalfHourEntryIndex = currentHalfHourEntryIndex; + this.currentSlot = currentSlot; this.checksum = checksum; - this.remainingEighthSecondsInCurrentHalfHourEntry = remainingEighthSecondsInCurrentHalfHourEntry; - this.remainingPulsesInCurrentHalfHourEntry = remainingPulsesInCurrentHalfHourEntry; + this.remainingEighthSecondsInCurrentSlot = remainingEighthSecondsInCurrentSlot; + this.remainingPulsesInCurrentSlot = remainingPulsesInCurrentSlot; this.deliveryType = deliveryType; - this.interlockCommand = interlockCommand; } - public short getLength() { + short getLength() { return (short) (insulinProgramElements.size() * 6 + 10); } - public byte getBodyLength() { + byte getBodyLength() { return (byte) (insulinProgramElements.size() * 6 + 8); } @Override public byte[] getEncoded() { - ByteBuffer commandBuffer = ByteBuffer.allocate(this.getLength()) // + ByteBuffer buffer = ByteBuffer.allocate(this.getLength()) // .put(commandType.getValue()) // .put(getBodyLength()) // .putInt(nonce) // .put(deliveryType.getValue()) // .putShort(checksum) // - .put(currentHalfHourEntryIndex) // - .putShort(remainingEighthSecondsInCurrentHalfHourEntry) // - .putShort(remainingPulsesInCurrentHalfHourEntry); + .put(currentSlot) // + .putShort(remainingEighthSecondsInCurrentSlot) // + .putShort(remainingPulsesInCurrentSlot); - for (InsulinProgramElement element : insulinProgramElements) { - commandBuffer.put(element.getEncoded()); + for (ShortInsulinProgramElement element : insulinProgramElements) { + buffer.put(element.getEncoded()); } - byte[] command = commandBuffer.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(); + return buffer.array(); } - public static final class Builder extends NonceEnabledCommandBuilder { - private List insulinProgramElements; - private Byte currentHalfOurEntryIndex; - private Short checksum; - private Short remainingEighthSecondsInCurrentHalfHourEntry; - private Short remainingPulsesInCurrentHalfHourEntry; - private DeliveryType deliveryType; - private Command interlockCommand; - - public Builder setInsulinProgramElements(List 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 { + enum DeliveryType { BASAL((byte) 0x00), TEMP_BASAL((byte) 0x01), BOLUS((byte) 0x02); @@ -167,12 +72,11 @@ public final class ProgramInsulinCommand extends NonceEnabledCommand { @Override public String toString() { return "ProgramInsulinCommand{" + "insulinProgramElements=" + insulinProgramElements + - ", currentHalfHourEntryIndex=" + currentHalfHourEntryIndex + + ", currentSlot=" + currentSlot + ", checksum=" + checksum + - ", remainingEighthSecondsInCurrentHalfHourEntry=" + remainingEighthSecondsInCurrentHalfHourEntry + - ", remainingPulsesInCurrentHalfHourEntry=" + remainingPulsesInCurrentHalfHourEntry + + ", remainingEighthSecondsInCurrentSlot=" + remainingEighthSecondsInCurrentSlot + + ", remainingPulsesInCurrentSlot=" + remainingPulsesInCurrentSlot + ", deliveryType=" + deliveryType + - ", interlockCommand=" + interlockCommand + ", nonce=" + nonce + ", commandType=" + commandType + ", 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(); - } - } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SetUniqueIdCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SetUniqueIdCommand.java index 7b36a22cd3..bdf7c0fe05 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SetUniqueIdCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SetUniqueIdCommand.java @@ -17,7 +17,7 @@ public final class SetUniqueIdCommand extends HeaderEnabledCommand { private final int podSequenceNumber; 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); this.lotNumber = lotNumber; this.podSequenceNumber = podSequenceNumber; diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SilenceAlertsCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SilenceAlertsCommand.java index b818ff34ad..9fe21103f3 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SilenceAlertsCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/SilenceAlertsCommand.java @@ -14,7 +14,7 @@ public final class SilenceAlertsCommand extends NonceEnabledCommand { 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); this.parameters = parameters; } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/StopDeliveryCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/StopDeliveryCommand.java index e4ea66a55f..db6bf0d29f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/StopDeliveryCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/StopDeliveryCommand.java @@ -16,7 +16,7 @@ public final class StopDeliveryCommand extends NonceEnabledCommand { private final DeliveryType deliveryType; 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); this.deliveryType = deliveryType; this.beepType = beepType; diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentSlot.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentSlot.java new file mode 100644 index 0000000000..71ff653df7 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentSlot.java @@ -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 + + '}'; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/LongInsulinProgramElement.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/LongInsulinProgramElement.java new file mode 100644 index 0000000000..60c05fa476 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/LongInsulinProgramElement.java @@ -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 + + '}'; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ProgramBasalUtil.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ProgramBasalUtil.java new file mode 100644 index 0000000000..54aef93b11 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ProgramBasalUtil.java @@ -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 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 mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) { + if (pulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { + throw new IllegalArgumentException("Basal program must contain 48 slots"); + } + + List 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; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ShortInsulinProgramElement.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ShortInsulinProgramElement.java new file mode 100644 index 0000000000..08768b684e --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ShortInsulinProgramElement.java @@ -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 + + '}'; + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/BasalProgram.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/BasalProgram.java new file mode 100644 index 0000000000..5019260b0b --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/definition/BasalProgram.java @@ -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 segments; + + public BasalProgram(List 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 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 + + '}'; + } +} diff --git a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/response/NakResponseTest.java b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/response/NakResponseTest.java index 8f43916574..06937a0e92 100644 --- a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/response/NakResponseTest.java +++ b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/response/NakResponseTest.java @@ -16,8 +16,8 @@ public class NakResponseTest { @Test public void testValidResponse() throws DecoderException { byte[] encoded = Hex.decodeHex("0603070009"); - NakResponse response = new NakResponse(encoded); + assertArrayEquals(encoded, response.getEncoded()); assertNotSame(encoded, response.getEncoded()); assertEquals(ResponseType.NAK_RESPONSE, response.getResponseType());