From 5347ae4f2deebdf0c4bcc8cf8abf10004f587b41 Mon Sep 17 00:00:00 2001 From: Bart Sopers Date: Thu, 18 Feb 2021 00:31:25 +0100 Subject: [PATCH] WIP on Omnipod program temp basal command --- .../pod/command/ProgramBasalCommand.java | 14 +- .../pod/command/ProgramBolusCommand.java | 6 +- .../pod/command/ProgramInsulinCommand.java | 2 +- .../pod/command/ProgramTempBasalCommand.java | 152 ++++++++++++++++++ .../program/BasalInsulinProgramElement.java | 4 + ...=> CurrentBasalInsulinProgramElement.java} | 4 +- .../TempBasalInsulinProgramElement.java | 35 ++++ .../program/{ => util}/ProgramBasalUtil.java | 49 +++--- .../program/util/ProgramTempBasalUtil.java | 71 ++++++++ .../dash/driver/pod/util/MessageUtil.java | 2 +- .../command/ProgramTempBasalCommandTest.java | 44 +++++ 11 files changed, 351 insertions(+), 32 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommand.java rename omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/{CurrentLongInsulinProgramElement.java => CurrentBasalInsulinProgramElement.java} (84%) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/TempBasalInsulinProgramElement.java rename omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/{ => util}/ProgramBasalUtil.java (77%) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.java create mode 100644 omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.java 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 47ca0871c8..a79f7b7374 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 @@ -9,10 +9,10 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.b import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.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.CurrentBasalInsulinProgramElement; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentSlot; -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.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.util.ProgramBasalUtil; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder; @@ -115,18 +115,18 @@ public final class ProgramBasalCommand extends HeaderEnabledCommand { short[] pulsesPerSlot = ProgramBasalUtil.mapBasalProgramToPulsesPerSlot(basalProgram); CurrentSlot currentSlot = ProgramBasalUtil.calculateCurrentSlot(pulsesPerSlot, currentTime); - short checksum = ProgramBasalUtil.createChecksum(pulsesPerSlot, currentSlot); - List longInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram)); + short checksum = ProgramBasalUtil.calculateChecksum(pulsesPerSlot, currentSlot); + List longInsulinProgramElements = ProgramBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(ProgramBasalUtil.mapBasalProgramToTenthPulsesPerSlot(basalProgram)); List shortInsulinProgramElements = ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); - CurrentLongInsulinProgramElement currentLongInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime); + CurrentBasalInsulinProgramElement currentBasalInsulinProgramElement = ProgramBasalUtil.calculateCurrentLongInsulinProgramElement(longInsulinProgramElements, currentTime); ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, shortInsulinProgramElements, checksum, currentSlot.getIndex(), currentSlot.getEighthSecondsRemaining(), currentSlot.getPulsesRemaining(), ProgramInsulinCommand.DeliveryType.BASAL); return new ProgramBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, - longInsulinProgramElements, programReminder, currentLongInsulinProgramElement.getIndex(), - currentLongInsulinProgramElement.getRemainingTenthPulses(), currentLongInsulinProgramElement.getDelayUntilNextTenthPulseInUsec()); + longInsulinProgramElements, programReminder, currentBasalInsulinProgramElement.getIndex(), + currentBasalInsulinProgramElement.getRemainingTenthPulses(), currentBasalInsulinProgramElement.getDelayUntilNextTenthPulseInUsec()); } } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBolusCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBolusCommand.java index 4431993e56..7c1e4a80bd 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBolusCommand.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramBolusCommand.java @@ -103,7 +103,7 @@ public final class ProgramBolusCommand extends HeaderEnabledCommand { short byte10And11 = (short) (numberOfPulses * delayBetweenPulsesInEighthSeconds); ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, - Collections.singletonList(new BolusShortInsulinProgramElement(numberOfPulses)), createChecksum((byte) 0x01, byte10And11, numberOfPulses), + Collections.singletonList(new BolusShortInsulinProgramElement(numberOfPulses)), calculateChecksum((byte) 0x01, byte10And11, numberOfPulses), (byte) 0x01, byte10And11, (short) numberOfPulses, ProgramInsulinCommand.DeliveryType.BOLUS); int delayUntilFirstTenthPulseInUsec = delayBetweenPulsesInEighthSeconds / 8 * 100_000; @@ -112,8 +112,8 @@ public final class ProgramBolusCommand extends HeaderEnabledCommand { } } - private static short createChecksum(byte numberOfSlots, short byte10And11, short numberOfPulses) { - return MessageUtil.createCheckSum(ByteBuffer.allocate(7) // + private static short calculateChecksum(byte numberOfSlots, short byte10And11, short numberOfPulses) { + return MessageUtil.calculateChecksum(ByteBuffer.allocate(7) // .put(numberOfSlots) // .putShort(byte10And11) // .putShort(numberOfPulses) // 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 fe759b1ab4..ce060000cc 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 @@ -69,7 +69,7 @@ final class ProgramInsulinCommand extends NonceEnabledCommand { } } - public short createChecksum(byte[] bytes) { + public short calculateChecksum(byte[] bytes) { short sum = 0; for (byte b : bytes) { sum += (short) (b & 0xff); diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommand.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommand.java new file mode 100644 index 0000000000..42031a189b --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommand.java @@ -0,0 +1,152 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.HeaderEnabledCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.builder.NonceEnabledCommandBuilder; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.BasalInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util.ProgramBasalUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util.ProgramTempBasalUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder; + +// NOT SUPPORTED: percentage temp basal +public final class ProgramTempBasalCommand extends HeaderEnabledCommand { + private final ProgramInsulinCommand interlockCommand; + private final ProgramReminder programReminder; + private final List insulinProgramElements; + private final TempBasalMethod tempBasalMethod; + + protected ProgramTempBasalCommand(ProgramInsulinCommand interlockCommand, int uniqueId, short sequenceNumber, boolean multiCommandFlag, ProgramReminder programReminder, + List insulinProgramElements, TempBasalMethod tempBasalMethod) { + super(CommandType.PROGRAM_TEMP_BASAL, uniqueId, sequenceNumber, multiCommandFlag); + this.interlockCommand = interlockCommand; + this.programReminder = programReminder; + this.insulinProgramElements = new ArrayList<>(insulinProgramElements); + this.tempBasalMethod = tempBasalMethod; + } + + public TempBasalMethod getTempBasalMethod() { + return tempBasalMethod; + } + + public byte getBodyLength() { + byte bodyLength = (byte) (insulinProgramElements.size() * 6 + 8); + + if (tempBasalMethod == TempBasalMethod.SECOND_METHOD) { + return bodyLength; + } + + // TempBasalMethod.FIRST_METHOD + for (BasalInsulinProgramElement element : insulinProgramElements) { + if (element.getTotalTenthPulses() == 0 && element.getNumberOfSlots() > 1) { + bodyLength = (byte) ((element.getNumberOfSlots() - 1) * 6 + bodyLength); + } + } + + return bodyLength; + } + + public short getLength() { + return (short) (getBodyLength() + 2); + } + + @Override public byte[] getEncoded() { + BasalInsulinProgramElement firstProgramElement = insulinProgramElements.get(0); + + short remainingTenthPulsesInFirstElement; + int delayUntilNextTenthPulseInUsec; + + if (firstProgramElement.getTotalTenthPulses() == 0) { + if (tempBasalMethod == TempBasalMethod.FIRST_METHOD) { + remainingTenthPulsesInFirstElement = 0; + } else { + remainingTenthPulsesInFirstElement = firstProgramElement.getNumberOfSlots(); + } + delayUntilNextTenthPulseInUsec = ProgramBasalUtil.NUMBER_OF_USEC_IN_SLOT; + } else { + remainingTenthPulsesInFirstElement = firstProgramElement.getTotalTenthPulses(); + delayUntilNextTenthPulseInUsec = (int) (firstProgramElement.getNumberOfSlots() * 1_800.0d / remainingTenthPulsesInFirstElement * 1_000_000); + } + + ByteBuffer buffer = ByteBuffer.allocate(getLength()) // + .put(commandType.getValue()) // + .put(getBodyLength()) // + .put(programReminder.getEncoded()) // + .put((byte) 0x00) // Current slot index + .putShort(remainingTenthPulsesInFirstElement) // + .putInt(delayUntilNextTenthPulseInUsec); + + for (BasalInsulinProgramElement element : insulinProgramElements) { + buffer.put(element.getEncoded()); + } + + byte[] tempBasalCommand = buffer.array(); + byte[] interlockCommand = this.interlockCommand.getEncoded(); + byte[] header = encodeHeader(uniqueId, sequenceNumber, (short) (tempBasalCommand.length + interlockCommand.length), multiCommandFlag); + + return appendCrc(ByteBuffer.allocate(header.length + interlockCommand.length + tempBasalCommand.length) // + .put(header) // + .put(interlockCommand) // + .put(tempBasalCommand) // + .array()); + } + + public static class Builder extends NonceEnabledCommandBuilder { + private ProgramReminder programReminder; + private Double rateInUnitsPerHour; + private Short durationInMinutes; + + public Builder setProgramReminder(ProgramReminder programReminder) { + this.programReminder = programReminder; + return this; + } + + public Builder setRateInUnitsPerHour(double rateInUnitsPerHour) { + this.rateInUnitsPerHour = rateInUnitsPerHour; + return this; + } + + public Builder setDurationInMinutes(short durationInMinutes) { + if (durationInMinutes % 30 != 0) { + throw new IllegalArgumentException("durationInMinutes must be dividable by 30"); + } + this.durationInMinutes = durationInMinutes; + return this; + } + + @Override protected ProgramTempBasalCommand buildCommand() { + if (programReminder == null) { + throw new IllegalArgumentException("programReminder can not be null"); + } + if (rateInUnitsPerHour == null) { + throw new IllegalArgumentException("rateInUnitsPerHour can not be null"); + } + if (durationInMinutes == null) { + throw new IllegalArgumentException("durationInMinutes can not be null"); + } + + byte durationInSlots = (byte) (durationInMinutes % 30); + short[] pulsesPerSlot = ProgramTempBasalUtil.mapTempBasalToPulsesPerSlot(durationInSlots, rateInUnitsPerHour); + short[] tenthPulsesPerSlot = ProgramTempBasalUtil.mapTempBasalToTenthPulsesPerSlot(durationInSlots, rateInUnitsPerHour); + TempBasalMethod tempBasalMethod = tenthPulsesPerSlot[0] == 0 ? TempBasalMethod.SECOND_METHOD : TempBasalMethod.FIRST_METHOD; + + List shortInsulinProgramElements = ProgramTempBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); + List insulinProgramElements = ProgramTempBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, tempBasalMethod); + + ProgramInsulinCommand interlockCommand = new ProgramInsulinCommand(uniqueId, sequenceNumber, multiCommandFlag, nonce, shortInsulinProgramElements, + ProgramTempBasalUtil.calculateChecksum(durationInSlots, pulsesPerSlot[0], pulsesPerSlot), durationInSlots, + (short) 0x3840, pulsesPerSlot[0], ProgramInsulinCommand.DeliveryType.TEMP_BASAL); + + return new ProgramTempBasalCommand(interlockCommand, uniqueId, sequenceNumber, multiCommandFlag, programReminder, insulinProgramElements, tempBasalMethod); + } + } + + public enum TempBasalMethod { + FIRST_METHOD, + SECOND_METHOD + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/BasalInsulinProgramElement.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/BasalInsulinProgramElement.java index 01359b4586..78020c2f6f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/BasalInsulinProgramElement.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/BasalInsulinProgramElement.java @@ -32,6 +32,10 @@ public class BasalInsulinProgramElement implements Encodable { return numberOfSlots; } + public short getDurationInSeconds() { + return (short) (numberOfSlots * 1_800); + } + public short getTotalTenthPulses() { return totalTenthPulses; } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentLongInsulinProgramElement.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentBasalInsulinProgramElement.java similarity index 84% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentLongInsulinProgramElement.java rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentBasalInsulinProgramElement.java index 5a976a33ee..fb7bcd3215 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentLongInsulinProgramElement.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/CurrentBasalInsulinProgramElement.java @@ -1,11 +1,11 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program; -public class CurrentLongInsulinProgramElement { +public class CurrentBasalInsulinProgramElement { private final byte index; private final int delayUntilNextTenthPulseInUsec; private final short remainingTenthPulses; - public CurrentLongInsulinProgramElement(byte index, int delayUntilNextTenthPulseInUsec, short remainingTenthPulses) { + public CurrentBasalInsulinProgramElement(byte index, int delayUntilNextTenthPulseInUsec, short remainingTenthPulses) { this.index = index; this.delayUntilNextTenthPulseInUsec = delayUntilNextTenthPulseInUsec; this.remainingTenthPulses = remainingTenthPulses; diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/TempBasalInsulinProgramElement.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/TempBasalInsulinProgramElement.java new file mode 100644 index 0000000000..c95203b43d --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/TempBasalInsulinProgramElement.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program; + +import java.nio.ByteBuffer; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.ProgramTempBasalCommand; + +public class TempBasalInsulinProgramElement extends BasalInsulinProgramElement { + private final ProgramTempBasalCommand.TempBasalMethod tempBasalMethod; + + public TempBasalInsulinProgramElement(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec, ProgramTempBasalCommand.TempBasalMethod tempBasalMethod) { + super(startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec); + this.tempBasalMethod = tempBasalMethod; + } + + @Override public byte[] getEncoded() { + ByteBuffer buffer = ByteBuffer.allocate(6); + if (getTotalTenthPulses() == 0) { + if (tempBasalMethod == ProgramTempBasalCommand.TempBasalMethod.FIRST_METHOD) { + for (int i = 0; i < getNumberOfSlots(); i++) { + buffer.putShort((short) 0) // + .putInt((int) ((long) getDurationInSeconds() * 1_000_000d / getNumberOfSlots())); + } + } else { + // Zero basal and temp basal second method + buffer.putShort(getNumberOfSlots()) // + .putInt((int) ((long) getDurationInSeconds() * 1_000_000d / getNumberOfSlots())); + } + } else { + buffer.putShort(getTotalTenthPulses()) // + .putInt(getDelayBetweenTenthPulsesInUsec()); + } + return buffer.array(); + } + +} 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/util/ProgramBasalUtil.java similarity index 77% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/ProgramBasalUtil.java rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramBasalUtil.java index 13d1c0f395..a238554714 100644 --- 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/util/ProgramBasalUtil.java @@ -1,4 +1,4 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program; +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -6,20 +6,33 @@ import java.util.Calendar; import java.util.Date; import java.util.List; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.BasalInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.BasalShortInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentBasalInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.CurrentSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram; import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil; public final class ProgramBasalUtil { - private static final byte NUMBER_OF_BASAL_SLOTS = 48; - private static final byte MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT = 16; - private static final int NUMBER_OF_USEC_IN_SLOT = 1_800_000_000; + public static final int NUMBER_OF_USEC_IN_SLOT = 1_800_000_000; + public static final byte NUMBER_OF_BASAL_SLOTS = 48; + public static final byte MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT = 16; private ProgramBasalUtil() { } - public static List mapPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) { - if (tenthPulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { - throw new IllegalArgumentException("Basal program must contain 48 slots"); + public interface BasalInsulinProgramElementFactory { + T create(byte startSlotIndex, byte numberOfSlots, short totalTenthPulses, int delayBetweenTenthPulsesInUsec); + } + + public static List mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot) { + return mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, BasalInsulinProgramElement::new); + } + + public static List mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot, BasalInsulinProgramElementFactory insulinProgramElementFactory) { + if (tenthPulsesPerSlot.length > NUMBER_OF_BASAL_SLOTS) { + throw new IllegalArgumentException("Basal program must contain at most 48 slots"); } List elements = new ArrayList<>(); @@ -27,12 +40,12 @@ public final class ProgramBasalUtil { byte numberOfSlotsInCurrentElement = 0; byte startSlotIndex = 0; - for (int i = 0; i < NUMBER_OF_BASAL_SLOTS; i++) { + for (int i = 0; i < tenthPulsesPerSlot.length; i++) { if (i == 0) { previousTenthPulsesPerSlot = tenthPulsesPerSlot[i]; numberOfSlotsInCurrentElement = 1; } else if (previousTenthPulsesPerSlot != tenthPulsesPerSlot[i] || (numberOfSlotsInCurrentElement + 1) * previousTenthPulsesPerSlot > 65_534) { - elements.add(new BasalInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); + elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); previousTenthPulsesPerSlot = tenthPulsesPerSlot[i]; numberOfSlotsInCurrentElement = 1; @@ -41,14 +54,14 @@ public final class ProgramBasalUtil { numberOfSlotsInCurrentElement++; } } - elements.add(new BasalInsulinProgramElement(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); + elements.add(insulinProgramElementFactory.create(startSlotIndex, numberOfSlotsInCurrentElement, (short) (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement), (int) (((long) NUMBER_OF_USEC_IN_SLOT * numberOfSlotsInCurrentElement) / (previousTenthPulsesPerSlot * numberOfSlotsInCurrentElement)))); return elements; } public static List mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) { - if (pulsesPerSlot.length != NUMBER_OF_BASAL_SLOTS) { - throw new IllegalArgumentException("Basal program must contain 48 slots"); + if (pulsesPerSlot.length > NUMBER_OF_BASAL_SLOTS) { + throw new IllegalArgumentException("Basal program must contain at most 48 slots"); } List elements = new ArrayList<>(); @@ -57,7 +70,7 @@ public final class ProgramBasalUtil { byte numberOfSlotsInCurrentElement = 0; byte currentTotalNumberOfSlots = 0; - while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) { + while (currentTotalNumberOfSlots < pulsesPerSlot.length) { if (currentTotalNumberOfSlots == 0) { // First slot @@ -83,7 +96,7 @@ public final class ProgramBasalUtil { boolean expectAlternatePulseForNextSegment = false; currentTotalNumberOfSlots++; extraAlternatePulse = true; - while (currentTotalNumberOfSlots < NUMBER_OF_BASAL_SLOTS) { + while (currentTotalNumberOfSlots < pulsesPerSlot.length) { // Loop rest alternate pulse segment if (pulsesPerSlot[currentTotalNumberOfSlots] == previousPulsesPerSlot + (expectAlternatePulseForNextSegment ? 1 : 0)) { @@ -182,7 +195,7 @@ public final class ProgramBasalUtil { return new CurrentSlot(index, (short) (secondsRemaining * 8), pulsesRemaining); } - public static CurrentLongInsulinProgramElement calculateCurrentLongInsulinProgramElement(List elements, Date currentTime) { + public static CurrentBasalInsulinProgramElement calculateCurrentLongInsulinProgramElement(List elements, Date currentTime) { Calendar instance = Calendar.getInstance(); instance.setTime(currentTime); @@ -218,7 +231,7 @@ public final class ProgramBasalUtil { } short remainingTenthPulses = (short) ((remainingTenThousandthPulses % 1_000 != 0 ? 1 : 0) + remainingTenThousandthPulses / 1_000); - return new CurrentLongInsulinProgramElement(index, delayUntilNextTenthPulseInUsec, remainingTenthPulses); + return new CurrentBasalInsulinProgramElement(index, delayUntilNextTenthPulseInUsec, remainingTenthPulses); } index++; @@ -227,7 +240,7 @@ public final class ProgramBasalUtil { throw new IllegalStateException("Could not determine current long insulin program element"); } - public static short createChecksum(short[] pulsesPerSlot, CurrentSlot currentSlot) { + public static short calculateChecksum(short[] pulsesPerSlot, CurrentSlot currentSlot) { ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + 2 + NUMBER_OF_BASAL_SLOTS * 2) // .put(currentSlot.getIndex()) // .putShort(currentSlot.getPulsesRemaining()) // @@ -237,6 +250,6 @@ public final class ProgramBasalUtil { buffer.putShort(pulses); } - return MessageUtil.createCheckSum(buffer.array()); + return MessageUtil.calculateChecksum(buffer.array()); } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.java new file mode 100644 index 0000000000..29b9172397 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/insulin/program/util/ProgramTempBasalUtil.java @@ -0,0 +1,71 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.util; + +import java.nio.ByteBuffer; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.ProgramTempBasalCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.BasalInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.ShortInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.insulin.program.TempBasalInsulinProgramElement; +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.MessageUtil; + +public final class ProgramTempBasalUtil { + private ProgramTempBasalUtil() { + } + + public static List mapTenthPulsesPerSlotToLongInsulinProgramElements(short[] tenthPulsesPerSlot, ProgramTempBasalCommand.TempBasalMethod tempBasalMethod) { + return ProgramBasalUtil.mapTenthPulsesPerSlotToLongInsulinProgramElements(tenthPulsesPerSlot, + (startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec) -> + new TempBasalInsulinProgramElement(startSlotIndex, numberOfSlots, totalTenthPulses, delayBetweenTenthPulsesInUsec, tempBasalMethod)); + } + + public static short[] mapTempBasalToTenthPulsesPerSlot(int durationInSlots, double rateInUnitsPerHour) { + short pulsesPerHour = (short) Math.round(rateInUnitsPerHour * 20); + + short[] tenthPulsesPerSlot = new short[durationInSlots]; + for (int i = 0; durationInSlots > i; i++) { + tenthPulsesPerSlot[i] = (short) (roundToHalf(pulsesPerHour / 2.0d) * 10); + } + + return tenthPulsesPerSlot; + } + + private static double roundToHalf(double d) { + return (double) (short) ((short) (int) (d * 10.0d) / 5 * 5) / 10.0d; + } + + public static short[] mapTempBasalToPulsesPerSlot(byte durationInSlots, double rateInUnitsPerHour) { + short pulsesPerHour = (short) Math.round(rateInUnitsPerHour * 20); + short[] pulsesPerSlot = new short[durationInSlots]; + + boolean remainingPulse = false; + + for (int i = 0; durationInSlots > i; i++) { + pulsesPerSlot[i] = (short) (pulsesPerHour / 2); + if (pulsesPerHour % 2 == 1) { // Do extra alternate pulse + if (remainingPulse) { + pulsesPerSlot[i] += 1; + } + remainingPulse = !remainingPulse; + } + } + + return pulsesPerSlot; + } + + public static short calculateChecksum(byte totalNumberOfSlots, short pulsesInFirstSlot, short[] pulsesPerSlot) { + ByteBuffer buffer = ByteBuffer.allocate(1 + 2 + 2 + 2 * pulsesPerSlot.length) // + .put(totalNumberOfSlots) // + .putShort((short) 0x3840) // + .putShort(pulsesInFirstSlot); + for (short pulses : pulsesPerSlot) { + buffer.putShort(pulses); + } + + return MessageUtil.calculateChecksum(buffer.array()); + } + + public static List mapPulsesPerSlotToShortInsulinProgramElements(short[] pulsesPerSlot) { + return ProgramBasalUtil.mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot); + } +} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/util/MessageUtil.java b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/util/MessageUtil.java index f67993e3c5..6d9297f12a 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/util/MessageUtil.java +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/util/MessageUtil.java @@ -30,7 +30,7 @@ public class MessageUtil { return s; } - public static short createCheckSum(byte[] bytes) { + public static short calculateChecksum(byte[] bytes) { short sum = 0; for (byte b : bytes) { sum += (short) (b & 0xff); diff --git a/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.java b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.java new file mode 100644 index 0000000000..d239f0ab2f --- /dev/null +++ b/omnipod-dash/src/test/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/pod/command/ProgramTempBasalCommandTest.java @@ -0,0 +1,44 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.junit.Test; + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.ProgramReminder; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class ProgramTempBasalCommandTest { + @Test + public void testFirstTempBasalMethod() throws DecoderException { + ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() // + .setUniqueId(37879809) // + .setNonce(1229869870) // + .setSequenceNumber((short) 3) // + .setRateInUnitsPerHour(5d) // + .setDurationInMinutes((short) 60) // + .setProgramReminder(new ProgramReminder(false, true, (byte) 0)) // + .build(); + + assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod()); + + assertArrayEquals(Hex.decodeHex("024200010C201A0E494E532E01014303384000322032160E400005DC0036EE8005DC0036EE808396"), command.getEncoded()); + } + + @Test + public void testSecondTempBasalMethod() throws DecoderException { + ProgramTempBasalCommand command = new ProgramTempBasalCommand.Builder() // + .setUniqueId(37879809) // + .setNonce(1229869870) // + .setSequenceNumber((short) 13) // + .setRateInUnitsPerHour(0.0) // + .setDurationInMinutes((short) 60) // + .setProgramReminder(new ProgramReminder(true, true, (byte) 0)) // + .build(); + + assertEquals(ProgramTempBasalCommand.TempBasalMethod.SECOND_METHOD, command.getTempBasalMethod()); + + assertArrayEquals(Hex.decodeHex("0242000134201A0E494E532E01007B03384000002000160EC00000036B49D2000003EB49D2000223"), command.getEncoded()); + } +} \ No newline at end of file