Add files from Omnipod test app
This commit is contained in:
parent
ca325c18e9
commit
aa15c80924
|
@ -63,17 +63,33 @@ public abstract class RileyLinkCommunicationManager {
|
||||||
// All pump communications go through this function.
|
// All pump communications go through this function.
|
||||||
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Class<E> clazz)
|
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Class<E> clazz)
|
||||||
throws RileyLinkCommunicationException {
|
throws RileyLinkCommunicationException {
|
||||||
|
return sendAndListen(msg, timeout_ms, null, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, Integer extendPreamble_ms, Class<E> clazz)
|
||||||
|
throws RileyLinkCommunicationException {
|
||||||
|
return sendAndListen(msg, timeout_ms, 0, extendPreamble_ms, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility
|
||||||
|
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, Integer extendPreamble_ms, Class<E> clazz)
|
||||||
|
throws RileyLinkCommunicationException {
|
||||||
|
return sendAndListen(msg, timeout_ms, repeatCount, 0, extendPreamble_ms, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <E extends RLMessage> E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, int retryCount, Integer extendPreamble_ms, Class<E> clazz)
|
||||||
|
throws RileyLinkCommunicationException {
|
||||||
|
|
||||||
if (showPumpMessages) {
|
if (showPumpMessages) {
|
||||||
if (isLogEnabled())
|
if (isLogEnabled())
|
||||||
LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData()));
|
LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), timeout_ms);
|
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()),
|
||||||
|
(byte)0, (byte)repeatCount, (byte)0, (byte)0, timeout_ms, (byte)retryCount, extendPreamble_ms);
|
||||||
|
|
||||||
RadioResponse radioResponse = rfSpyResponse.getRadioResponse();
|
RadioResponse radioResponse = rfSpyResponse.getRadioResponse();
|
||||||
|
E response = createResponseMessage(radioResponse.getPayload(), clazz);
|
||||||
E response = createResponseMessage(rfSpyResponse.getRadioResponse().getPayload(), clazz);
|
|
||||||
|
|
||||||
if (response.isValid()) {
|
if (response.isValid()) {
|
||||||
// Mark this as the last time we heard from the pump.
|
// Mark this as the last time we heard from the pump.
|
||||||
|
@ -399,8 +415,10 @@ public abstract class RileyLinkCommunicationManager {
|
||||||
lastGoodReceiverCommunicationTime = System.currentTimeMillis();
|
lastGoodReceiverCommunicationTime = System.currentTimeMillis();
|
||||||
|
|
||||||
SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime);
|
SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime);
|
||||||
|
if(pumpStatus != null) {
|
||||||
pumpStatus.setLastCommunicationToNow();
|
pumpStatus.setLastCommunicationToNow();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private long getLastGoodReceiverCommunicationTime() {
|
private long getLastGoodReceiverCommunicationTime() {
|
||||||
|
|
|
@ -7,11 +7,9 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.Rile
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class RileyLinkCommunicationException extends Exception {
|
public class RileyLinkCommunicationException extends Exception {
|
||||||
|
|
||||||
String extendedErrorText;
|
String extendedErrorText;
|
||||||
private RileyLinkBLEError errorCode;
|
private RileyLinkBLEError errorCode;
|
||||||
|
|
||||||
|
|
||||||
public RileyLinkCommunicationException(RileyLinkBLEError errorCode, String extendedErrorText) {
|
public RileyLinkCommunicationException(RileyLinkBLEError errorCode, String extendedErrorText) {
|
||||||
super(errorCode.getDescription());
|
super(errorCode.getDescription());
|
||||||
|
|
||||||
|
@ -27,4 +25,7 @@ public class RileyLinkCommunicationException extends Exception {
|
||||||
// this.extendedErrorText = extendedErrorText;
|
// this.extendedErrorText = extendedErrorText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RileyLinkBLEError getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.common.utils;
|
package info.nightscout.androidaps.plugins.pump.common.utils;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -28,6 +29,14 @@ public class ByteUtil {
|
||||||
return (b < 0) ? b + 256 : b;
|
return (b < 0) ? b + 256 : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] getBytesFromInt16(int value) {
|
||||||
|
byte[] array = getBytesFromInt(value);
|
||||||
|
return new byte[] {array[2], array[3]};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getBytesFromInt(int value) {
|
||||||
|
return ByteBuffer.allocate(4).putInt(value).array();
|
||||||
|
}
|
||||||
|
|
||||||
/* For Reference: static void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) */
|
/* For Reference: static void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) */
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.data.Profile;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AcknowledgeAlertsAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.CancelDeliveryAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.DeactivatePodAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetPodInfoAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetStatusAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.InsertCannulaAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.PairAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.PrimeAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetTempBasalAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PairService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.SetTempBasalService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfo;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalScheduleMapper;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
public class OmnipodManager {
|
||||||
|
private final OmnipodCommunicationService communicationService;
|
||||||
|
private PodSessionState podState;
|
||||||
|
|
||||||
|
public OmnipodManager(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
if (communicationService == null) {
|
||||||
|
throw new IllegalArgumentException("Communication service cannot be null");
|
||||||
|
}
|
||||||
|
this.communicationService = communicationService;
|
||||||
|
this.podState = podState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OmnipodManager(OmnipodCommunicationService communicationService) {
|
||||||
|
this(communicationService, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OmnipodCommunicationService getCommunicationService() {
|
||||||
|
return communicationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PodInfo> T getPodInfo(PodInfoType podInfoType) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
PodInfoResponse podInfoResponse = communicationService.executeAction(new GetPodInfoAction(podState, podInfoType));
|
||||||
|
return podInfoResponse.getPodInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse getStatus() {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalStateException("Pod should be paired first");
|
||||||
|
}
|
||||||
|
return communicationService.executeAction(new GetStatusAction(podState));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acknowledgeAlerts() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new AcknowledgeAlertsAction(podState, podState.getActiveAlerts()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pairAndPrime() {
|
||||||
|
if (podState == null) {
|
||||||
|
podState = communicationService.executeAction(new PairAction(new PairService()));
|
||||||
|
}
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
|
||||||
|
communicationService.executeAction(new PrimeAction(new PrimeService(), podState));
|
||||||
|
|
||||||
|
executeDelayed(() -> {
|
||||||
|
StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState));
|
||||||
|
PrimeAction.updatePrimingStatus(podState, delayedStatusResponse);
|
||||||
|
}, OmnipodConst.POD_PRIME_DURATION);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Illegal setup state: " + podState.getSetupProgress().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertCannula(Profile profile) {
|
||||||
|
if (podState == null || podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
|
||||||
|
throw new IllegalArgumentException("Pod should be paired and primed first");
|
||||||
|
} else if (podState.getSetupProgress().isAfter(SetupProgress.CANNULA_INSERTING)) {
|
||||||
|
throw new IllegalStateException("Illegal setup state: " + podState.getSetupProgress().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
communicationService.executeAction(new InsertCannulaAction(new InsertCannulaService(), podState,
|
||||||
|
BasalScheduleMapper.mapProfileToBasalSchedule(profile)));
|
||||||
|
|
||||||
|
executeDelayed(() -> {
|
||||||
|
StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState));
|
||||||
|
InsertCannulaAction.updateCannulaInsertionStatus(podState, delayedStatusResponse);
|
||||||
|
}, OmnipodConst.POD_CANNULA_INSERTION_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBasalSchedule(BasalSchedule basalSchedule, boolean confidenceReminder) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new SetBasalScheduleAction(podState, basalSchedule,
|
||||||
|
confidenceReminder, podState.getScheduleOffset(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTempBasal(double rate, Duration duration) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new SetTempBasalAction(new SetTempBasalService(),
|
||||||
|
podState, rate, duration, true, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelTempBasal() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.TEMP_BASAL, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bolus(double units) {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new BolusAction(podState, units, true, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelBolus() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.BOLUS, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void suspendDelivery() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new CancelDeliveryAction(podState, EnumSet.allOf(DeliveryType.class), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resumeDelivery() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new SetBasalScheduleAction(podState, podState.getBasalSchedule(),
|
||||||
|
true, podState.getScheduleOffset(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime() {
|
||||||
|
if (!isInitialized()) {
|
||||||
|
throw new IllegalStateException("Pod should be initialized first");
|
||||||
|
}
|
||||||
|
// Suspend delivery
|
||||||
|
communicationService.executeAction(new CancelDeliveryAction(podState, EnumSet.allOf(DeliveryType.class), false));
|
||||||
|
|
||||||
|
// Joda seems to cache the default time zone, so we use the JVM's
|
||||||
|
DateTimeZone.setDefault(DateTimeZone.forTimeZone(TimeZone.getDefault()));
|
||||||
|
podState.setTimeZone(DateTimeZone.getDefault());
|
||||||
|
|
||||||
|
// Resume delivery
|
||||||
|
communicationService.executeAction(new SetBasalScheduleAction(podState, podState.getBasalSchedule(),
|
||||||
|
true, podState.getScheduleOffset(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getTime() {
|
||||||
|
return podState.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deactivatePod() {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalStateException("Pod should be paired first");
|
||||||
|
}
|
||||||
|
communicationService.executeAction(new DeactivatePodAction(podState, true));
|
||||||
|
resetPodState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInitialized() {
|
||||||
|
return podState != null && podState.getSetupProgress() == SetupProgress.COMPLETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPodStateAsString() {
|
||||||
|
return podState == null ? "null" : podState.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetPodState() {
|
||||||
|
podState = null;
|
||||||
|
SP.remove(OmnipodConst.Prefs.PodState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeDelayed(Runnable r, Duration timeout) {
|
||||||
|
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
||||||
|
scheduledExecutorService.schedule(r, timeout.getMillis(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.OmnipodAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodPacket;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.NotEnoughDataException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/29/18.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class OmnipodCommunicationService extends RileyLinkCommunicationManager {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(OmnipodCommunicationService.class);
|
||||||
|
|
||||||
|
public OmnipodCommunicationService(RFSpy rfspy) {
|
||||||
|
super(rfspy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void configurePumpSpecificSettings() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryToConnectToDevice() {
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] createPumpMessageContent(RLMessageType type) {
|
||||||
|
return new byte[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <E extends RLMessage> E createResponseMessage(byte[] payload, Class<E> clazz) {
|
||||||
|
return (E) new OmnipodPacket(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends MessageBlock> T sendCommand(Class<T> responseClass, PodState podState, MessageBlock command) {
|
||||||
|
OmnipodMessage message = new OmnipodMessage(podState.getAddress(), Collections.singletonList(command), podState.getMessageNumber());
|
||||||
|
return exchangeMessages(responseClass, podState, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience method
|
||||||
|
public <T> T executeAction(OmnipodAction<T> action) {
|
||||||
|
return action.execute(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message) {
|
||||||
|
return exchangeMessages(responseClass, podState, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
|
||||||
|
for (int i = 0; 2 > i; i++) {
|
||||||
|
|
||||||
|
if (podState.hasNonceState() && message.isNonceResyncable()) {
|
||||||
|
podState.advanceToNextNonce();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBlock responseMessageBlock = transportMessages(podState, message, addressOverride, ackAddressOverride);
|
||||||
|
|
||||||
|
if (responseMessageBlock instanceof StatusResponse) {
|
||||||
|
podState.updateFromStatusResponse((StatusResponse) responseMessageBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseClass.isInstance(responseMessageBlock)) {
|
||||||
|
return (T) responseMessageBlock;
|
||||||
|
} else {
|
||||||
|
if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) {
|
||||||
|
ErrorResponse error = (ErrorResponse) responseMessageBlock;
|
||||||
|
if (error.getErrorResponseType() == ErrorResponseType.BAD_NONCE) {
|
||||||
|
podState.resyncNonce(error.getNonceSearchKey(), message.getSentNonce(), message.getSequenceNumber());
|
||||||
|
message.resyncNonce(podState.getCurrentNonce());
|
||||||
|
} else {
|
||||||
|
throw new PodReturnedErrorResponseException((ErrorResponse) responseMessageBlock);
|
||||||
|
}
|
||||||
|
} else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) {
|
||||||
|
PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo();
|
||||||
|
LOG.error("Pod fault: " + faultEvent.getFaultEventCode().name());
|
||||||
|
podState.setFaultEvent(faultEvent);
|
||||||
|
throw new PodFaultException(faultEvent);
|
||||||
|
} else {
|
||||||
|
throw new OmnipodException("Unexpected response type: " + responseMessageBlock.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new OmnipodException("Nonce resync failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageBlock transportMessages(PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
|
||||||
|
int packetAddress = podState.getAddress();
|
||||||
|
if (addressOverride != null) {
|
||||||
|
packetAddress = addressOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstPacket = true;
|
||||||
|
byte[] encodedMessage = message.getEncoded();
|
||||||
|
|
||||||
|
OmnipodPacket response = null;
|
||||||
|
while (encodedMessage.length > 0) {
|
||||||
|
PacketType packetType = firstPacket ? PacketType.PDM : PacketType.CON;
|
||||||
|
OmnipodPacket packet = new OmnipodPacket(packetAddress, packetType, podState.getPacketNumber(), encodedMessage);
|
||||||
|
byte[] encodedMessageInPacket = packet.getEncodedMessage();
|
||||||
|
//getting the data remaining to be sent
|
||||||
|
encodedMessage = ByteUtil.substring(encodedMessage, encodedMessageInPacket.length, encodedMessage.length - encodedMessageInPacket.length);
|
||||||
|
firstPacket = false;
|
||||||
|
try {
|
||||||
|
response = exchangePackets(podState, packet);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new OmnipodException("Failed to exchange packets", ex);
|
||||||
|
}
|
||||||
|
//We actually ignore (ack) responses if it is not last packet to send
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getPacketType() == PacketType.ACK) {
|
||||||
|
podState.increasePacketNumber(1);
|
||||||
|
throw new OmnipodException("Received ack instead of real response");
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnipodMessage receivedMessage = null;
|
||||||
|
byte[] receivedMessageData = response.getEncodedMessage();
|
||||||
|
while (receivedMessage == null) {
|
||||||
|
try {
|
||||||
|
receivedMessage = OmnipodMessage.decodeMessage(receivedMessageData);
|
||||||
|
} catch (NotEnoughDataException ex) {
|
||||||
|
// Message is (probably) not complete yet
|
||||||
|
OmnipodPacket ackForCon = createAckPacket(podState, packetAddress, ackAddressOverride);
|
||||||
|
try {
|
||||||
|
OmnipodPacket conPacket = exchangePackets(podState, ackForCon, 3, 40);
|
||||||
|
if (conPacket.getPacketType() != PacketType.CON) {
|
||||||
|
throw new OmnipodException("Received a non-con packet type: " + conPacket.getPacketType());
|
||||||
|
}
|
||||||
|
receivedMessageData = ByteUtil.concat(receivedMessageData, conPacket.getEncodedMessage());
|
||||||
|
} catch (RileyLinkCommunicationException ex2) {
|
||||||
|
throw new OmnipodException("RileyLink communication failed", ex2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
podState.increaseMessageNumber(2);
|
||||||
|
|
||||||
|
ackUntilQuiet(podState, packetAddress, ackAddressOverride);
|
||||||
|
|
||||||
|
List<MessageBlock> messageBlocks = receivedMessage.getMessageBlocks();
|
||||||
|
|
||||||
|
if (messageBlocks.size() == 0) {
|
||||||
|
throw new OmnipodException("Not enough data");
|
||||||
|
} else if (messageBlocks.size() > 1) {
|
||||||
|
LOG.error("received more than one message block: " + messageBlocks.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return messageBlocks.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmnipodPacket createAckPacket(PodState podState, Integer packetAddress, Integer messageAddress) {
|
||||||
|
int pktAddress = podState.getAddress();
|
||||||
|
int msgAddress = podState.getAddress();
|
||||||
|
if (packetAddress != null) {
|
||||||
|
pktAddress = packetAddress;
|
||||||
|
}
|
||||||
|
if (messageAddress != null) {
|
||||||
|
msgAddress = messageAddress;
|
||||||
|
}
|
||||||
|
return new OmnipodPacket(pktAddress, PacketType.ACK, podState.getPacketNumber(), ByteUtil.getBytesFromInt(msgAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ackUntilQuiet(PodState podState, Integer packetAddress, Integer messageAddress) {
|
||||||
|
OmnipodPacket ack = createAckPacket(podState, packetAddress, messageAddress);
|
||||||
|
boolean quiet = false;
|
||||||
|
while (!quiet) try {
|
||||||
|
sendAndListen(ack, 300, 1, 0, 40, OmnipodPacket.class);
|
||||||
|
} catch (RileyLinkCommunicationException ex) {
|
||||||
|
if (RileyLinkBLEError.Timeout.equals(ex.getErrorCode())) {
|
||||||
|
quiet = true;
|
||||||
|
} else {
|
||||||
|
LOG.debug("Ignoring exception in ackUntilQuiet: " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.debug("Ignoring exception in ackUntilQuiet: " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
podState.increasePacketNumber(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet) throws RileyLinkCommunicationException {
|
||||||
|
return exchangePackets(podState, packet, 0, 333, 9000, 127);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int preambleExtensionMilliseconds) throws RileyLinkCommunicationException {
|
||||||
|
return exchangePackets(podState, packet, repeatCount, 333, 9000, preambleExtensionMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int responseTimeoutMilliseconds, int exchangeTimeoutMilliseconds, int preambleExtensionMilliseconds) throws RileyLinkCommunicationException {
|
||||||
|
long timeoutTime = System.currentTimeMillis() + exchangeTimeoutMilliseconds;
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() < timeoutTime) {
|
||||||
|
OmnipodPacket response = null;
|
||||||
|
try {
|
||||||
|
response = sendAndListen(packet, responseTimeoutMilliseconds, repeatCount, 9, preambleExtensionMilliseconds, OmnipodPacket.class);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.debug("Ignoring exception in exchangePackets: " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
|
||||||
|
}
|
||||||
|
if (response == null || !response.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (response.getAddress() != packet.getAddress()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (response.getSequenceNumber() != ((podState.getPacketNumber() + 1) & 0b11111)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
podState.increasePacketNumber(2);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
throw new OmnipodException("Timeout when trying to exchange packets");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AcknowledgeAlertsCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class AcknowledgeAlertsAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final AlertSet alerts;
|
||||||
|
|
||||||
|
public AcknowledgeAlertsAction(PodSessionState podState, AlertSet alerts) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (alerts == null) {
|
||||||
|
throw new IllegalArgumentException("Alert set can not be null");
|
||||||
|
} else if (alerts.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("Alert set can not be empty");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.alerts = alerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AcknowledgeAlertsAction(PodSessionState podState, AlertSlot alertSlot) {
|
||||||
|
this(podState, new AlertSet(Collections.singletonList(alertSlot)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
return communicationService.sendCommand(StatusResponse.class, podState,
|
||||||
|
new AcknowledgeAlertsCommand(podState.getCurrentNonce(), alerts));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BolusExtraCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class BolusAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final double units;
|
||||||
|
private final Duration timeBetweenPulses;
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
private final boolean completionBeep;
|
||||||
|
|
||||||
|
public BolusAction(PodSessionState podState, double units, Duration timeBetweenPulses,
|
||||||
|
boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (timeBetweenPulses == null) {
|
||||||
|
throw new IllegalArgumentException("Time between pulses cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.units = units;
|
||||||
|
this.timeBetweenPulses = timeBetweenPulses;
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BolusAction(PodSessionState podState, double units, boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
this(podState, units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
BolusDeliverySchedule bolusDeliverySchedule = new BolusDeliverySchedule(units, timeBetweenPulses);
|
||||||
|
SetInsulinScheduleCommand setInsulinScheduleCommand = new SetInsulinScheduleCommand(
|
||||||
|
podState.getCurrentNonce(), bolusDeliverySchedule);
|
||||||
|
BolusExtraCommand bolusExtraCommand = new BolusExtraCommand(units, timeBetweenPulses,
|
||||||
|
acknowledgementBeep, completionBeep);
|
||||||
|
OmnipodMessage primeBolusMessage = new OmnipodMessage(podState.getAddress(),
|
||||||
|
Arrays.asList(setInsulinScheduleCommand, bolusExtraCommand), podState.getMessageNumber());
|
||||||
|
return communicationService.exchangeMessages(StatusResponse.class, podState, primeBolusMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.CancelDeliveryCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class CancelDeliveryAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final EnumSet<DeliveryType> deliveryTypes;
|
||||||
|
private final BeepType beepType;
|
||||||
|
|
||||||
|
public CancelDeliveryAction(PodSessionState podState, EnumSet<DeliveryType> deliveryTypes,
|
||||||
|
boolean acknowledgementBeep) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (deliveryTypes == null) {
|
||||||
|
throw new IllegalArgumentException("Delivery types cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.deliveryTypes = deliveryTypes;
|
||||||
|
if (acknowledgementBeep) {
|
||||||
|
beepType = BeepType.BIP_BIP;
|
||||||
|
} else {
|
||||||
|
beepType = BeepType.NO_BEEP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CancelDeliveryAction(PodSessionState podState, DeliveryType deliveryType,
|
||||||
|
boolean acknowledgementBeep) {
|
||||||
|
this(podState, EnumSet.of(deliveryType), acknowledgementBeep);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
return communicationService.sendCommand(StatusResponse.class, podState,
|
||||||
|
new CancelDeliveryCommand(podState.getCurrentNonce(), beepType, deliveryTypes));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigureAlertsCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class ConfigureAlertsAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final List<AlertConfiguration> alertConfigurations;
|
||||||
|
|
||||||
|
public ConfigureAlertsAction(PodSessionState podState, List<AlertConfiguration> alertConfigurations) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (alertConfigurations == null) {
|
||||||
|
throw new IllegalArgumentException("Alert configurations cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.alertConfigurations = alertConfigurations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
ConfigureAlertsCommand configureAlertsCommand = new ConfigureAlertsCommand(podState.getCurrentNonce(), alertConfigurations);
|
||||||
|
StatusResponse statusResponse = communicationService.sendCommand(StatusResponse.class, podState, configureAlertsCommand);
|
||||||
|
for (AlertConfiguration alertConfiguration : alertConfigurations) {
|
||||||
|
podState.putConfiguredAlert(alertConfiguration.getAlertSlot(), alertConfiguration.getAlertType());
|
||||||
|
}
|
||||||
|
return statusResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.DeactivatePodCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class DeactivatePodAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
|
||||||
|
public DeactivatePodAction(PodSessionState podState, boolean acknowledgementBeep) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
if (!podState.isSuspended() && !podState.hasFaultEvent()) {
|
||||||
|
communicationService.executeAction(new CancelDeliveryAction(podState,
|
||||||
|
EnumSet.allOf(DeliveryType.class), acknowledgementBeep));
|
||||||
|
}
|
||||||
|
|
||||||
|
DeactivatePodCommand deactivatePodCommand = new DeactivatePodCommand(podState.getCurrentNonce());
|
||||||
|
return communicationService.sendCommand(StatusResponse.class, podState, deactivatePodCommand);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class GetPodInfoAction implements OmnipodAction<PodInfoResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final PodInfoType podInfoType;
|
||||||
|
|
||||||
|
public GetPodInfoAction(PodSessionState podState, PodInfoType podInfoType) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (podInfoType == null) {
|
||||||
|
throw new IllegalArgumentException("Pod info type cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.podInfoType = podInfoType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
return communicationService.sendCommand(PodInfoResponse.class, podState, new GetStatusCommand(podInfoType));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class GetStatusAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
|
||||||
|
public GetStatusAction(PodSessionState podState) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
return communicationService.sendCommand(StatusResponse.class, podState, new GetStatusCommand(PodInfoType.NORMAL));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class InsertCannulaAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(InsertCannulaAction.class);
|
||||||
|
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final InsertCannulaService service;
|
||||||
|
private final BasalSchedule initialBasalSchedule;
|
||||||
|
|
||||||
|
public InsertCannulaAction(InsertCannulaService insertCannulaService, PodSessionState podState, BasalSchedule initialBasalSchedule) {
|
||||||
|
if (insertCannulaService == null) {
|
||||||
|
throw new IllegalArgumentException("Insert cannula service cannot be null");
|
||||||
|
}
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (initialBasalSchedule == null) {
|
||||||
|
throw new IllegalArgumentException("Initial basal schedule cannot be null");
|
||||||
|
}
|
||||||
|
this.service = insertCannulaService;
|
||||||
|
this.podState = podState;
|
||||||
|
this.initialBasalSchedule = initialBasalSchedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateCannulaInsertionStatus(PodSessionState podState, StatusResponse statusResponse) {
|
||||||
|
if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING) &&
|
||||||
|
statusResponse.getPodProgressStatus().isReadyForDelivery()) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Updating SetupProgress from CANNULA_INSERTING to COMPLETED");
|
||||||
|
}
|
||||||
|
podState.setSetupProgress(SetupProgress.COMPLETED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
|
||||||
|
throw new IllegalStateException("Pod should be primed first");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.INITIAL_BASAL_SCHEDULE_SET)) {
|
||||||
|
service.programInitialBasalSchedule(communicationService, podState, initialBasalSchedule);
|
||||||
|
podState.setSetupProgress(SetupProgress.INITIAL_BASAL_SCHEDULE_SET);
|
||||||
|
}
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_INSERT_CANNULA)) {
|
||||||
|
service.executeExpirationRemindersAlertCommand(communicationService, podState);
|
||||||
|
podState.setSetupProgress(SetupProgress.STARTING_INSERT_CANNULA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.CANNULA_INSERTING)) {
|
||||||
|
StatusResponse statusResponse = service.executeInsertionBolusCommand(communicationService, podState);
|
||||||
|
podState.setSetupProgress(SetupProgress.CANNULA_INSERTING);
|
||||||
|
return statusResponse;
|
||||||
|
} else if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING)) {
|
||||||
|
// Check status
|
||||||
|
StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState));
|
||||||
|
updateCannulaInsertionStatus(podState, statusResponse);
|
||||||
|
return statusResponse;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Illegal setup progress: " + podState.getSetupProgress().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
|
||||||
|
public interface OmnipodAction<T> {
|
||||||
|
T execute(OmnipodCommunicationService communicationService);
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PairService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState;
|
||||||
|
|
||||||
|
public class PairAction implements OmnipodAction<PodSessionState> {
|
||||||
|
private final PairService service;
|
||||||
|
private final int address;
|
||||||
|
|
||||||
|
public PairAction(PairService pairService, int address) {
|
||||||
|
if (pairService == null) {
|
||||||
|
throw new IllegalArgumentException("Pair service cannot be null");
|
||||||
|
}
|
||||||
|
this.service = pairService;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PairAction(PairService service) {
|
||||||
|
this(service, generateRandomAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int generateRandomAddress() {
|
||||||
|
return 0x1f000000 | (new Random().nextInt() & 0x000fffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodSessionState execute(OmnipodCommunicationService communicationService) {
|
||||||
|
PodSetupState setupState = new PodSetupState(address, 0x00, 0x00);
|
||||||
|
|
||||||
|
VersionResponse assignAddressResponse = service.executeAssignAddressCommand(communicationService, setupState);
|
||||||
|
|
||||||
|
DateTimeZone timeZone = DateTimeZone.getDefault();
|
||||||
|
DateTime activationDate = DateTime.now(timeZone);
|
||||||
|
|
||||||
|
VersionResponse confirmPairingResponse = service.executeConfigurePodCommand(communicationService, setupState,
|
||||||
|
assignAddressResponse.getLot(), assignAddressResponse.getTid(), activationDate);
|
||||||
|
|
||||||
|
PodSessionState podState = new PodSessionState(timeZone, address, activationDate, confirmPairingResponse.getPiVersion(),
|
||||||
|
confirmPairingResponse.getPmVersion(), confirmPairingResponse.getLot(), confirmPairingResponse.getTid(),
|
||||||
|
setupState.getPacketNumber(), setupState.getMessageNumber());
|
||||||
|
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
|
||||||
|
|
||||||
|
return podState;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class PrimeAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PrimeAction.class);
|
||||||
|
|
||||||
|
private final PrimeService service;
|
||||||
|
private final PodSessionState podState;
|
||||||
|
|
||||||
|
public PrimeAction(PrimeService primeService, PodSessionState podState) {
|
||||||
|
if (primeService == null) {
|
||||||
|
throw new IllegalArgumentException("Prime service cannot be null");
|
||||||
|
}
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
this.service = primeService;
|
||||||
|
this.podState = podState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updatePrimingStatus(PodSessionState podState, StatusResponse statusResponse) {
|
||||||
|
if (podState.getSetupProgress().equals(SetupProgress.PRIMING) && statusResponse.getPodProgressStatus().equals(PodProgressStatus.READY_FOR_BASAL_SCHEDULE)) {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Updating SetupProgress from PRIMING to PRIMING_FINISHED");
|
||||||
|
}
|
||||||
|
podState.setSetupProgress(SetupProgress.PRIMING_FINISHED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.POD_CONFIGURED)) {
|
||||||
|
throw new IllegalStateException("Pod should be paired first");
|
||||||
|
}
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_PRIME)) {
|
||||||
|
service.executeDisableTab5Sub16FaultConfigCommand(communicationService, podState);
|
||||||
|
service.executeFinishSetupReminderAlertCommand(communicationService, podState);
|
||||||
|
podState.setSetupProgress(SetupProgress.STARTING_PRIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING)) {
|
||||||
|
StatusResponse statusResponse = service.executePrimeBolusCommand(communicationService, podState);
|
||||||
|
podState.setSetupProgress(SetupProgress.PRIMING);
|
||||||
|
return statusResponse;
|
||||||
|
} else if (podState.getSetupProgress().equals(SetupProgress.PRIMING)) {
|
||||||
|
// Check status
|
||||||
|
StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState));
|
||||||
|
updatePrimingStatus(podState, statusResponse);
|
||||||
|
return statusResponse;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Illegal setup progress: " + podState.getSetupProgress().name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BasalScheduleExtraCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class SetBasalScheduleAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final BasalSchedule basalSchedule;
|
||||||
|
private final boolean confidenceReminder;
|
||||||
|
private final Duration scheduleOffset;
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
|
||||||
|
public SetBasalScheduleAction(PodSessionState podState, BasalSchedule basalSchedule,
|
||||||
|
boolean confidenceReminder, Duration scheduleOffset, boolean acknowledgementBeep) {
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (basalSchedule == null) {
|
||||||
|
throw new IllegalArgumentException("Basal schedule cannot be null");
|
||||||
|
}
|
||||||
|
if (scheduleOffset == null) {
|
||||||
|
throw new IllegalArgumentException("Schedule offset cannot be null");
|
||||||
|
}
|
||||||
|
this.podState = podState;
|
||||||
|
this.basalSchedule = basalSchedule;
|
||||||
|
this.confidenceReminder = confidenceReminder;
|
||||||
|
this.scheduleOffset = scheduleOffset;
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
SetInsulinScheduleCommand setBasal = new SetInsulinScheduleCommand(podState.getCurrentNonce(), basalSchedule, scheduleOffset);
|
||||||
|
BasalScheduleExtraCommand extraCommand = new BasalScheduleExtraCommand(basalSchedule, scheduleOffset,
|
||||||
|
acknowledgementBeep, confidenceReminder, Duration.ZERO);
|
||||||
|
OmnipodMessage basalMessage = new OmnipodMessage(podState.getAddress(), Arrays.asList(setBasal, extraCommand),
|
||||||
|
podState.getMessageNumber());
|
||||||
|
|
||||||
|
StatusResponse statusResponse = communicationService.exchangeMessages(StatusResponse.class, podState, basalMessage);
|
||||||
|
podState.setBasalSchedule(basalSchedule);
|
||||||
|
return statusResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.SetTempBasalService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class SetTempBasalAction implements OmnipodAction<StatusResponse> {
|
||||||
|
private final SetTempBasalService service;
|
||||||
|
private final PodSessionState podState;
|
||||||
|
private final double rate;
|
||||||
|
private final Duration duration;
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
private final boolean completionBeep;
|
||||||
|
|
||||||
|
public SetTempBasalAction(SetTempBasalService setTempBasalService, PodSessionState podState,
|
||||||
|
double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
if (setTempBasalService == null) {
|
||||||
|
throw new IllegalArgumentException("Set temp basal service cannot be null");
|
||||||
|
}
|
||||||
|
if (podState == null) {
|
||||||
|
throw new IllegalArgumentException("Pod state cannot be null");
|
||||||
|
}
|
||||||
|
if (duration == null) {
|
||||||
|
throw new IllegalArgumentException("Duration cannot be null");
|
||||||
|
}
|
||||||
|
this.service = setTempBasalService;
|
||||||
|
this.podState = podState;
|
||||||
|
this.rate = rate;
|
||||||
|
this.duration = duration;
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatusResponse execute(OmnipodCommunicationService communicationService) {
|
||||||
|
StatusResponse statusResponse = service.cancelTempBasal(communicationService, podState);
|
||||||
|
|
||||||
|
if (statusResponse.getDeliveryStatus() != DeliveryStatus.NORMAL) {
|
||||||
|
throw new IllegalStateException("Illegal delivery status: " +
|
||||||
|
statusResponse.getDeliveryStatus().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
return service.executeTempBasalCommand(communicationService, podState, rate, duration,
|
||||||
|
acknowledgementBeep, completionBeep);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class InsertCannulaService {
|
||||||
|
public StatusResponse programInitialBasalSchedule(OmnipodCommunicationService communicationService,
|
||||||
|
PodSessionState podState, BasalSchedule basalSchedule) {
|
||||||
|
return communicationService.executeAction(new SetBasalScheduleAction(podState, basalSchedule,
|
||||||
|
true, podState.getScheduleOffset(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse executeExpirationRemindersAlertCommand(OmnipodCommunicationService communicationService,
|
||||||
|
PodSessionState podState) {
|
||||||
|
DateTime endOfServiceTime = podState.getActivatedAt().plus(OmnipodConst.SERVICE_DURATION);
|
||||||
|
|
||||||
|
Duration timeUntilExpirationAdvisoryAlarm = new Duration(DateTime.now(),
|
||||||
|
endOfServiceTime.minus(OmnipodConst.END_OF_SERVICE_IMMINENT_WINDOW).minus(OmnipodConst.EXPIRATION_ADVISORY_WINDOW));
|
||||||
|
Duration timeUntilShutdownImminentAlarm = new Duration(DateTime.now(),
|
||||||
|
endOfServiceTime.minus(OmnipodConst.END_OF_SERVICE_IMMINENT_WINDOW));
|
||||||
|
|
||||||
|
AlertConfiguration expirationAdvisoryAlertConfiguration = AlertConfigurationFactory.createExpirationAdvisoryAlertConfiguration(
|
||||||
|
timeUntilExpirationAdvisoryAlarm, OmnipodConst.EXPIRATION_ADVISORY_WINDOW);
|
||||||
|
AlertConfiguration shutdownImminentAlertConfiguration = AlertConfigurationFactory.createShutdownImminentAlertConfiguration(
|
||||||
|
timeUntilShutdownImminentAlarm);
|
||||||
|
AlertConfiguration autoOffAlertConfiguration = AlertConfigurationFactory.createAutoOffAlertConfiguration(
|
||||||
|
false, Duration.ZERO);
|
||||||
|
|
||||||
|
List<AlertConfiguration> alertConfigurations = Arrays.asList( //
|
||||||
|
expirationAdvisoryAlertConfiguration, //
|
||||||
|
shutdownImminentAlertConfiguration, //
|
||||||
|
autoOffAlertConfiguration //
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ConfigureAlertsAction(podState, alertConfigurations).execute(communicationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse executeInsertionBolusCommand(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_CANNULA_INSERTION_BOLUS_UNITS,
|
||||||
|
Duration.standardSeconds(1), false, false));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AssignAddressCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigurePodCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class PairService {
|
||||||
|
public VersionResponse executeAssignAddressCommand(OmnipodCommunicationService communicationService, PodSetupState setupState) {
|
||||||
|
AssignAddressCommand assignAddress = new AssignAddressCommand(setupState.getAddress());
|
||||||
|
OmnipodMessage assignAddressMessage = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
|
||||||
|
Collections.singletonList(assignAddress), setupState.getMessageNumber());
|
||||||
|
|
||||||
|
return communicationService.exchangeMessages(VersionResponse.class, setupState, assignAddressMessage,
|
||||||
|
OmnipodConst.DEFAULT_ADDRESS, setupState.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionResponse executeConfigurePodCommand(OmnipodCommunicationService communicationService,
|
||||||
|
PodSetupState setupState, int lot, int tid, DateTime activationDate) {
|
||||||
|
// at this point for an unknown reason PDM starts counting messages from 0 again
|
||||||
|
setupState.setMessageNumber(0x00);
|
||||||
|
|
||||||
|
ConfigurePodCommand configurePodCommand = new ConfigurePodCommand(setupState.getAddress(), activationDate,
|
||||||
|
lot, tid);
|
||||||
|
OmnipodMessage message = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
|
||||||
|
Collections.singletonList(configurePodCommand), setupState.getMessageNumber());
|
||||||
|
VersionResponse configurePodResponse = communicationService.exchangeMessages(VersionResponse.class, setupState,
|
||||||
|
message, OmnipodConst.DEFAULT_ADDRESS, setupState.getAddress());
|
||||||
|
|
||||||
|
if (configurePodResponse.getPodProgressStatus() != PodProgressStatus.PAIRING_SUCCESS) {
|
||||||
|
throw new OmnipodException("Pairing failed, state: " + configurePodResponse.getPodProgressStatus().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurePodResponse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.FaultConfigCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class PrimeService {
|
||||||
|
|
||||||
|
public StatusResponse executeDisableTab5Sub16FaultConfigCommand(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
FaultConfigCommand faultConfigCommand = new FaultConfigCommand(podState.getCurrentNonce(), (byte) 0x00, (byte) 0x00);
|
||||||
|
OmnipodMessage faultConfigMessage = new OmnipodMessage(podState.getAddress(),
|
||||||
|
Collections.singletonList(faultConfigCommand), podState.getMessageNumber());
|
||||||
|
return communicationService.exchangeMessages(StatusResponse.class, podState, faultConfigMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse executeFinishSetupReminderAlertCommand(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
AlertConfiguration finishSetupReminderAlertConfiguration = AlertConfigurationFactory.createFinishSetupReminderAlertConfiguration();
|
||||||
|
return communicationService.executeAction(new ConfigureAlertsAction(podState,
|
||||||
|
Collections.singletonList(finishSetupReminderAlertConfiguration)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse executePrimeBolusCommand(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_PRIME_BOLUS_UNITS,
|
||||||
|
Duration.standardSeconds(1), false, false));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.CancelDeliveryAction;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.TempBasalExtraCommand;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
public class SetTempBasalService {
|
||||||
|
public StatusResponse cancelTempBasal(OmnipodCommunicationService communicationService, PodSessionState podState) {
|
||||||
|
return communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.TEMP_BASAL, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusResponse executeTempBasalCommand(OmnipodCommunicationService communicationService,
|
||||||
|
PodSessionState podState, double rate, Duration duration,
|
||||||
|
boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
List<MessageBlock> messageBlocks = Arrays.asList( //
|
||||||
|
new SetInsulinScheduleCommand(podState.getCurrentNonce(), rate, duration),
|
||||||
|
new TempBasalExtraCommand(rate, duration, acknowledgementBeep, completionBeep, Duration.ZERO));
|
||||||
|
|
||||||
|
OmnipodMessage message = new OmnipodMessage(podState.getAddress(), messageBlocks, podState.getMessageNumber());
|
||||||
|
return communicationService.exchangeMessages(StatusResponse.class, podState, message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
|
||||||
|
|
||||||
|
public interface IRawRepresentable {
|
||||||
|
byte[] getRawData();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public abstract class MessageBlock {
|
||||||
|
protected byte[] encodedData = new byte[0];
|
||||||
|
|
||||||
|
public MessageBlock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract MessageBlockType getType();
|
||||||
|
|
||||||
|
//This method returns raw message representation
|
||||||
|
//It should be rewritten in a derived class if raw representation of a concrete message
|
||||||
|
//is something else than just message type concatenated with message data
|
||||||
|
public byte[] getRawData() {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
stream.write(this.getType().getValue());
|
||||||
|
stream.write((byte) encodedData.length);
|
||||||
|
stream.write(encodedData);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return stream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
|
||||||
|
|
||||||
|
public abstract class NonceResyncableMessageBlock extends MessageBlock {
|
||||||
|
public abstract int getNonce();
|
||||||
|
|
||||||
|
public abstract void setNonce(int nonce);
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.CrcMismatchException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.NotEnoughDataException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC;
|
||||||
|
|
||||||
|
public class OmnipodMessage {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(OmnipodCommunicationService.class);
|
||||||
|
private final int address;
|
||||||
|
private final List<MessageBlock> messageBlocks;
|
||||||
|
private final int sequenceNumber;
|
||||||
|
|
||||||
|
public OmnipodMessage(int address, List<MessageBlock> messageBlocks, int sequenceNumber) {
|
||||||
|
this.address = address;
|
||||||
|
this.messageBlocks = messageBlocks;
|
||||||
|
this.sequenceNumber = sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OmnipodMessage decodeMessage(byte[] data) {
|
||||||
|
if (data.length < 10) {
|
||||||
|
throw new NotEnoughDataException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
int address = ByteUtil.toInt((int) data[0], (int) data[1], (int) data[2],
|
||||||
|
(int) data[3], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
byte b9 = data[4];
|
||||||
|
int bodyLength = ByteUtil.convertUnsignedByteToInt(data[5]);
|
||||||
|
if (data.length - 8 < bodyLength) {
|
||||||
|
throw new NotEnoughDataException("not enough data: " + ByteUtil.shortHexString(data));
|
||||||
|
}
|
||||||
|
int sequenceNumber = (((int) b9 >> 2) & 0b11111);
|
||||||
|
int crc = ByteUtil.toInt(data[data.length - 2], data[data.length - 1]);
|
||||||
|
int calculatedCrc = OmniCRC.crc16(ByteUtil.substring(data, 0, data.length - 2));
|
||||||
|
if (crc != calculatedCrc) {
|
||||||
|
throw new CrcMismatchException("CRC mismatch");
|
||||||
|
}
|
||||||
|
List<MessageBlock> blocks = decodeBlocks(ByteUtil.substring(data, 6, data.length - 6 - 2));
|
||||||
|
if (blocks == null || blocks.size() == 0) {
|
||||||
|
throw new OmnipodException("No blocks decoded");
|
||||||
|
}
|
||||||
|
|
||||||
|
OmnipodMessage result = new OmnipodMessage(address, blocks, sequenceNumber);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<MessageBlock> decodeBlocks(byte[] data) {
|
||||||
|
List<MessageBlock> blocks = new ArrayList<>();
|
||||||
|
int index = 0;
|
||||||
|
while (index < data.length) {
|
||||||
|
try {
|
||||||
|
MessageBlockType blockType = MessageBlockType.fromByte(data[index]);
|
||||||
|
MessageBlock block = blockType.decode(ByteUtil.substring(data, index));
|
||||||
|
blocks.add(block);
|
||||||
|
int blockLength = block.getRawData().length;
|
||||||
|
index += blockLength;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new OmnipodException("Failed to decode blocks", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
byte[] encodedData = new byte[0];
|
||||||
|
for (MessageBlock messageBlock : messageBlocks) {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, messageBlock.getRawData());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] header = new byte[0];
|
||||||
|
//right before the message blocks we have 6 bits of seqNum and 10 bits of length
|
||||||
|
header = ByteUtil.concat(header, ByteUtil.getBytesFromInt(address));
|
||||||
|
header = ByteUtil.concat(header, (byte) (((sequenceNumber & 0x1F) << 2) + ((encodedData.length >> 8) & 0x03)));
|
||||||
|
header = ByteUtil.concat(header, (byte) (encodedData.length & 0xFF));
|
||||||
|
encodedData = ByteUtil.concat(header, encodedData);
|
||||||
|
String myString = ByteUtil.shortHexString(encodedData);
|
||||||
|
int crc = OmniCRC.crc16(encodedData);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.substring(ByteUtil.getBytesFromInt(crc), 2, 2));
|
||||||
|
return encodedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MessageBlock> getMessageBlocks() {
|
||||||
|
return messageBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSequenceNumber() {
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "OmnipodMessage{" +
|
||||||
|
"address=" + address +
|
||||||
|
", encoded=" + ByteUtil.shortHexString(getEncoded()) +
|
||||||
|
", sequenceNumber=" + sequenceNumber +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNonceResyncable() {
|
||||||
|
return messageBlocks.size() > 0 && (messageBlocks.get(0) instanceof NonceResyncableMessageBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSentNonce() {
|
||||||
|
if (!isNonceResyncable()) {
|
||||||
|
throw new UnsupportedOperationException("Message is not nonce resyncable");
|
||||||
|
}
|
||||||
|
return ((NonceResyncableMessageBlock) messageBlocks.get(0)).getNonce();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resyncNonce(int nonce) {
|
||||||
|
for (MessageBlock messageBlock : messageBlocks) {
|
||||||
|
if (messageBlock instanceof NonceResyncableMessageBlock) {
|
||||||
|
((NonceResyncableMessageBlock) messageBlock).setNonce(nonce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.CrcMismatchException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by andy on 6/1/18.
|
||||||
|
*/
|
||||||
|
public class OmnipodPacket implements RLMessage {
|
||||||
|
private int packetAddress = 0;
|
||||||
|
private PacketType packetType = PacketType.INVALID;
|
||||||
|
private int sequenceNumber = 0;
|
||||||
|
private byte[] encodedMessage = null;
|
||||||
|
private boolean valid = false;
|
||||||
|
|
||||||
|
public OmnipodPacket(byte[] encoded) {
|
||||||
|
if (encoded.length < 7) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.packetAddress = ByteUtil.toInt((int) encoded[0], (int) encoded[1],
|
||||||
|
(int) encoded[2], (int) encoded[3], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
try {
|
||||||
|
this.packetType = PacketType.fromByte((byte) (((int) encoded[4] & 0xFF) >> 5));
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new OmnipodException("Invalid packet type", ex);
|
||||||
|
}
|
||||||
|
this.sequenceNumber = (encoded[4] & 0b11111);
|
||||||
|
byte crc = OmniCRC.crc8(ByteUtil.substring(encoded, 0, encoded.length - 1));
|
||||||
|
if (crc != encoded[encoded.length - 1]) {
|
||||||
|
throw new CrcMismatchException("CRC mismatch: " +
|
||||||
|
ByteUtil.shortHexString(new byte[]{crc}) + " <> " +
|
||||||
|
ByteUtil.shortHexString(new byte[]{encoded[encoded.length - 1]}) +
|
||||||
|
" (packetType=" + packetType.name() + ",packetLength=" + encoded.length + ")");
|
||||||
|
}
|
||||||
|
this.encodedMessage = ByteUtil.substring(encoded, 5, encoded.length - 1 - 5);
|
||||||
|
valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OmnipodPacket(int packetAddress, PacketType packetType, int packetNumber, byte[] encodedMessage) {
|
||||||
|
this.packetAddress = packetAddress;
|
||||||
|
this.packetType = packetType;
|
||||||
|
this.sequenceNumber = packetNumber;
|
||||||
|
this.encodedMessage = encodedMessage;
|
||||||
|
if (encodedMessage.length > packetType.getMaxBodyLength()) {
|
||||||
|
this.encodedMessage = ByteUtil.substring(encodedMessage, 0, packetType.getMaxBodyLength());
|
||||||
|
}
|
||||||
|
this.valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketType getPacketType() {
|
||||||
|
return packetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAddress() {
|
||||||
|
return packetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSequenceNumber() {
|
||||||
|
return sequenceNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEncodedMessage() {
|
||||||
|
return encodedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getTxData() {
|
||||||
|
byte[] output = new byte[0];
|
||||||
|
output = ByteUtil.concat(output, ByteUtil.getBytesFromInt(this.packetAddress));
|
||||||
|
output = ByteUtil.concat(output, (byte) ((this.packetType.getValue() << 5) + (sequenceNumber & 0b11111)));
|
||||||
|
output = ByteUtil.concat(output, encodedMessage);
|
||||||
|
output = ByteUtil.concat(output, OmniCRC.crc8(output));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class AcknowledgeAlertsCommand extends NonceResyncableMessageBlock {
|
||||||
|
|
||||||
|
private final AlertSet alerts;
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
public AcknowledgeAlertsCommand(int nonce, AlertSet alerts) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.alerts = alerts;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AcknowledgeAlertsCommand(int nonce, AlertSlot alertSlot) {
|
||||||
|
this(nonce, new AlertSet(Collections.singletonList(alertSlot)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.ACKNOWLEDGE_ALERT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = ByteUtil.getBytesFromInt(nonce);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, alerts.getRawValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class AssignAddressCommand extends MessageBlock {
|
||||||
|
private final int address;
|
||||||
|
|
||||||
|
public AssignAddressCommand(int address) {
|
||||||
|
this.address = address;
|
||||||
|
encodedData = ByteBuffer.allocate(4).putInt(this.address).array();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.ASSIGN_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class BasalScheduleExtraCommand extends MessageBlock {
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
private final boolean completionBeep;
|
||||||
|
private final Duration programReminderInterval;
|
||||||
|
private final byte currentEntryIndex;
|
||||||
|
private final double remainingPulses;
|
||||||
|
// We use a double for the delay between pulses because the Joda time API lacks precision for our calculations
|
||||||
|
private final double delayUntilNextTenthOfPulseInSeconds;
|
||||||
|
private final List<RateEntry> rateEntries;
|
||||||
|
|
||||||
|
public BasalScheduleExtraCommand(boolean acknowledgementBeep, boolean completionBeep,
|
||||||
|
Duration programReminderInterval, byte currentEntryIndex,
|
||||||
|
double remainingPulses, double delayUntilNextTenthOfPulseInSeconds, List<RateEntry> rateEntries) {
|
||||||
|
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
this.programReminderInterval = programReminderInterval;
|
||||||
|
this.currentEntryIndex = currentEntryIndex;
|
||||||
|
this.remainingPulses = remainingPulses;
|
||||||
|
this.delayUntilNextTenthOfPulseInSeconds = delayUntilNextTenthOfPulseInSeconds;
|
||||||
|
this.rateEntries = rateEntries;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalScheduleExtraCommand(BasalSchedule schedule, Duration scheduleOffset,
|
||||||
|
boolean acknowledgementBeep, boolean completionBeep, Duration programReminderInterval) {
|
||||||
|
rateEntries = new ArrayList<>();
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
this.programReminderInterval = programReminderInterval;
|
||||||
|
Duration scheduleOffsetNearestSecond = Duration.standardSeconds(Math.round(scheduleOffset.getMillis() / 1000.0));
|
||||||
|
|
||||||
|
BasalSchedule mergedSchedule = new BasalSchedule(schedule.adjacentEqualRatesMergedEntries());
|
||||||
|
List<BasalSchedule.BasalScheduleDurationEntry> durations = mergedSchedule.getDurations();
|
||||||
|
|
||||||
|
for (BasalSchedule.BasalScheduleDurationEntry entry : durations) {
|
||||||
|
rateEntries.addAll(RateEntry.createEntries(entry.getRate(), entry.getDuration()));
|
||||||
|
}
|
||||||
|
|
||||||
|
BasalSchedule.BasalScheduleLookupResult entryLookupResult = mergedSchedule.lookup(scheduleOffsetNearestSecond);
|
||||||
|
currentEntryIndex = (byte) entryLookupResult.getIndex();
|
||||||
|
double timeRemainingInEntryInSeconds = entryLookupResult.getStartTime().minus(scheduleOffsetNearestSecond.minus(entryLookupResult.getDuration())).getMillis() / 1000.0;
|
||||||
|
double rate = mergedSchedule.rateAt(scheduleOffsetNearestSecond);
|
||||||
|
int pulsesPerHour = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
double timeBetweenPulses = 3600.0 / pulsesPerHour;
|
||||||
|
delayUntilNextTenthOfPulseInSeconds = (timeRemainingInEntryInSeconds % (timeBetweenPulses / 10.0));
|
||||||
|
remainingPulses = pulsesPerHour * (timeRemainingInEntryInSeconds - delayUntilNextTenthOfPulseInSeconds) / 3600.0 + 0.1;
|
||||||
|
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
|
||||||
|
|
||||||
|
encodedData = new byte[]{
|
||||||
|
beepOptions,
|
||||||
|
currentEntryIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10)));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) Math.round(delayUntilNextTenthOfPulseInSeconds * 1000 * 1000)));
|
||||||
|
|
||||||
|
for (RateEntry entry : rateEntries) {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, entry.getRawData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.BASAL_SCHEDULE_EXTRA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAcknowledgementBeep() {
|
||||||
|
return acknowledgementBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompletionBeep() {
|
||||||
|
return completionBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getProgramReminderInterval() {
|
||||||
|
return programReminderInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getCurrentEntryIndex() {
|
||||||
|
return currentEntryIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRemainingPulses() {
|
||||||
|
return remainingPulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDelayUntilNextTenthOfPulseInSeconds() {
|
||||||
|
return delayUntilNextTenthOfPulseInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RateEntry> getRateEntries() {
|
||||||
|
return new ArrayList<>(rateEntries);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class BeepConfigCommand extends MessageBlock {
|
||||||
|
private final BeepType beepType;
|
||||||
|
private final boolean basalCompletionBeep;
|
||||||
|
private final Duration basalIntervalBeep;
|
||||||
|
private final boolean tempBasalCompletionBeep;
|
||||||
|
private final Duration tempBasalIntervalBeep;
|
||||||
|
private final boolean bolusCompletionBeep;
|
||||||
|
private final Duration bolusIntervalBeep;
|
||||||
|
|
||||||
|
public BeepConfigCommand(BeepType beepType, boolean basalCompletionBeep, Duration basalIntervalBeep,
|
||||||
|
boolean tempBasalCompletionBeep, Duration tempBasalIntervalBeep,
|
||||||
|
boolean bolusCompletionBeep, Duration bolusIntervalBeep) {
|
||||||
|
this.beepType = beepType;
|
||||||
|
this.basalCompletionBeep = basalCompletionBeep;
|
||||||
|
this.basalIntervalBeep = basalIntervalBeep;
|
||||||
|
this.tempBasalCompletionBeep = tempBasalCompletionBeep;
|
||||||
|
this.tempBasalIntervalBeep = tempBasalIntervalBeep;
|
||||||
|
this.bolusCompletionBeep = bolusCompletionBeep;
|
||||||
|
this.bolusIntervalBeep = bolusIntervalBeep;
|
||||||
|
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BeepConfigCommand(BeepType beepType) {
|
||||||
|
this(beepType, false, Duration.ZERO, false, Duration.ZERO, false, Duration.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = new byte[]{beepType.getValue()};
|
||||||
|
encodedData = ByteUtil.concat(encodedData, (byte) ((basalCompletionBeep ? (1 << 6) : 0) + (basalIntervalBeep.getStandardMinutes() & 0x3f)));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, (byte) ((tempBasalCompletionBeep ? (1 << 6) : 0) + (tempBasalIntervalBeep.getStandardMinutes() & 0x3f)));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, (byte) ((bolusCompletionBeep ? (1 << 6) : 0) + (bolusIntervalBeep.getStandardMinutes() & 0x3f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.BEEP_CONFIG;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class BolusExtraCommand extends MessageBlock {
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
private final boolean completionBeep;
|
||||||
|
private final Duration programReminderInterval;
|
||||||
|
private final double units;
|
||||||
|
private final Duration timeBetweenPulses;
|
||||||
|
private final double squareWaveUnits;
|
||||||
|
private final Duration squareWaveDuration;
|
||||||
|
|
||||||
|
public BolusExtraCommand(double units, boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
this(units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BolusExtraCommand(double units, Duration timeBetweenPulses, boolean acknowledgementBeep, boolean completionBeep) {
|
||||||
|
this(units, 0.0, Duration.ZERO, acknowledgementBeep, completionBeep, Duration.ZERO, timeBetweenPulses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BolusExtraCommand(double units, double squareWaveUnits, Duration squareWaveDuration,
|
||||||
|
boolean acknowledgementBeep, boolean completionBeep,
|
||||||
|
Duration programReminderInterval, Duration timeBetweenPulses) {
|
||||||
|
if (units <= 0D) {
|
||||||
|
throw new IllegalArgumentException("Units should be > 0");
|
||||||
|
} else if (units > OmnipodConst.MAX_BOLUS) {
|
||||||
|
throw new IllegalArgumentException("Units exceeds max bolus");
|
||||||
|
}
|
||||||
|
this.units = units;
|
||||||
|
this.squareWaveUnits = squareWaveUnits;
|
||||||
|
this.squareWaveDuration = squareWaveDuration;
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
this.programReminderInterval = programReminderInterval;
|
||||||
|
this.timeBetweenPulses = timeBetweenPulses;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
|
||||||
|
|
||||||
|
int squareWavePulseCountCountX10 = (int) Math.round(squareWaveUnits * 200);
|
||||||
|
int timeBetweenExtendedPulses = squareWavePulseCountCountX10 > 0 ? (int) squareWaveDuration.getMillis() * 100 / squareWavePulseCountCountX10 : 0;
|
||||||
|
|
||||||
|
encodedData = ByteUtil.concat(encodedData, beepOptions);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(units * 200)));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) timeBetweenPulses.getMillis() * 100));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(squareWavePulseCountCountX10));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(timeBetweenExtendedPulses));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.BOLUS_EXTRA;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class CancelDeliveryCommand extends NonceResyncableMessageBlock {
|
||||||
|
|
||||||
|
private final BeepType beepType;
|
||||||
|
private final EnumSet<DeliveryType> deliveryTypes;
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
public CancelDeliveryCommand(int nonce, BeepType beepType, EnumSet<DeliveryType> deliveryTypes) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.beepType = beepType;
|
||||||
|
this.deliveryTypes = deliveryTypes;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CancelDeliveryCommand(int nonce, BeepType beepType, DeliveryType deliveryType) {
|
||||||
|
this(nonce, beepType, EnumSet.of(deliveryType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.CANCEL_DELIVERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = new byte[5];
|
||||||
|
System.arraycopy(ByteUtil.getBytesFromInt(nonce), 0, encodedData, 0, 4);
|
||||||
|
byte beepTypeValue = beepType.getValue();
|
||||||
|
if (beepTypeValue > 8) {
|
||||||
|
beepTypeValue = 0;
|
||||||
|
}
|
||||||
|
encodedData[4] = (byte) ((beepTypeValue & 0x0F) << 4);
|
||||||
|
if (deliveryTypes.contains(DeliveryType.BASAL)) {
|
||||||
|
encodedData[4] |= 1;
|
||||||
|
}
|
||||||
|
if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) {
|
||||||
|
encodedData[4] |= 2;
|
||||||
|
}
|
||||||
|
if (deliveryTypes.contains(DeliveryType.BOLUS)) {
|
||||||
|
encodedData[4] |= 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class ConfigureAlertsCommand extends NonceResyncableMessageBlock {
|
||||||
|
private final List<AlertConfiguration> configurations;
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
public ConfigureAlertsCommand(int nonce, List<AlertConfiguration> configurations) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.configurations = configurations;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.CONFIGURE_ALERTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = ByteUtil.getBytesFromInt(nonce);
|
||||||
|
for (AlertConfiguration config : configurations) {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, config.getRawData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class ConfigurePodCommand extends MessageBlock {
|
||||||
|
|
||||||
|
private static final byte PACKET_TIMEOUT_LIMIT = 0x04;
|
||||||
|
|
||||||
|
private final int lot;
|
||||||
|
private final int tid;
|
||||||
|
private final DateTime date;
|
||||||
|
private final int address;
|
||||||
|
|
||||||
|
public ConfigurePodCommand(int address, DateTime date, int lot, int tid) {
|
||||||
|
this.address = address;
|
||||||
|
this.lot = lot;
|
||||||
|
this.tid = tid;
|
||||||
|
this.date = date;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.SETUP_POD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = new byte[0];
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(address));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, new byte[]{ //
|
||||||
|
(byte) 0x14, // unknown
|
||||||
|
PACKET_TIMEOUT_LIMIT, //
|
||||||
|
(byte) date.monthOfYear().get(), //
|
||||||
|
(byte) date.dayOfMonth().get(), //
|
||||||
|
(byte) (date.year().get() - 2000), //
|
||||||
|
(byte) date.hourOfDay().get(), //
|
||||||
|
(byte) date.minuteOfHour().get() //
|
||||||
|
});
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(lot));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(tid));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class DeactivatePodCommand extends NonceResyncableMessageBlock {
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
public DeactivatePodCommand(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.DEACTIVATE_POD;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = ByteUtil.getBytesFromInt(nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class FaultConfigCommand extends NonceResyncableMessageBlock {
|
||||||
|
private final byte tab5sub16;
|
||||||
|
private final byte tab5sub17;
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
public FaultConfigCommand(int nonce, byte tab5sub16, byte tab5sub17) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.tab5sub16 = tab5sub16;
|
||||||
|
this.tab5sub17 = tab5sub17;
|
||||||
|
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = ByteUtil.getBytesFromInt(nonce);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, tab5sub16);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, tab5sub17);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.FAULT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class GetStatusCommand extends MessageBlock {
|
||||||
|
private final PodInfoType podInfoType;
|
||||||
|
|
||||||
|
public GetStatusCommand(PodInfoType podInfoType) {
|
||||||
|
this.podInfoType = podInfoType;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = new byte[]{podInfoType.getValue()};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.GET_STATUS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliverySchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliveryTable;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.DeliverySchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.TempBasalDeliverySchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class SetInsulinScheduleCommand extends NonceResyncableMessageBlock {
|
||||||
|
|
||||||
|
private final DeliverySchedule schedule;
|
||||||
|
private int nonce;
|
||||||
|
|
||||||
|
// Bolus
|
||||||
|
public SetInsulinScheduleCommand(int nonce, BolusDeliverySchedule schedule) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.schedule = schedule;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basal schedule
|
||||||
|
public SetInsulinScheduleCommand(int nonce, BasalSchedule schedule, Duration scheduleOffset) {
|
||||||
|
int scheduleOffsetInSeconds = (int) scheduleOffset.getStandardSeconds();
|
||||||
|
|
||||||
|
BasalDeliveryTable table = new BasalDeliveryTable(schedule);
|
||||||
|
double rate = schedule.rateAt(scheduleOffset);
|
||||||
|
byte segment = (byte) (scheduleOffsetInSeconds / BasalDeliveryTable.SEGMENT_DURATION);
|
||||||
|
int segmentOffset = scheduleOffsetInSeconds % BasalDeliveryTable.SEGMENT_DURATION;
|
||||||
|
|
||||||
|
int timeRemainingInSegment = BasalDeliveryTable.SEGMENT_DURATION - segmentOffset;
|
||||||
|
|
||||||
|
double timeBetweenPulses = 3600 / (rate / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
|
||||||
|
double offsetToNextTenth = timeRemainingInSegment % (timeBetweenPulses / 10.0);
|
||||||
|
|
||||||
|
int pulsesRemainingInSegment = (int) ((timeRemainingInSegment + timeBetweenPulses / 10.0 - offsetToNextTenth) / timeBetweenPulses);
|
||||||
|
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.schedule = new BasalDeliverySchedule(segment, timeRemainingInSegment, pulsesRemainingInSegment, table);
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp basal
|
||||||
|
public SetInsulinScheduleCommand(int nonce, double tempBasalRate, Duration duration) {
|
||||||
|
if (tempBasalRate < 0D) {
|
||||||
|
throw new IllegalArgumentException("Rate should be >= 0");
|
||||||
|
} else if (tempBasalRate > OmnipodConst.MAX_BASAL_RATE) {
|
||||||
|
throw new IllegalArgumentException("Rate exceeds max basal rate");
|
||||||
|
}
|
||||||
|
if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) {
|
||||||
|
throw new IllegalArgumentException("Duration exceeds max temp basal duration");
|
||||||
|
}
|
||||||
|
int pulsesPerHour = (int) Math.round(tempBasalRate / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
int pulsesPerSegment = pulsesPerHour / 2;
|
||||||
|
this.nonce = nonce;
|
||||||
|
this.schedule = new TempBasalDeliverySchedule(BasalDeliveryTable.SEGMENT_DURATION, pulsesPerSegment, new BasalDeliveryTable(tempBasalRate, duration));
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
encodedData = ByteUtil.getBytesFromInt(nonce);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, schedule.getType().getValue());
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(schedule.getChecksum()));
|
||||||
|
encodedData = ByteUtil.concat(encodedData, schedule.getRawData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.SET_INSULIN_SCHEDULE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNonce() {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setNonce(int nonce) {
|
||||||
|
this.nonce = nonce;
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class TempBasalExtraCommand extends MessageBlock {
|
||||||
|
private final boolean acknowledgementBeep;
|
||||||
|
private final boolean completionBeep;
|
||||||
|
private final Duration programReminderInterval;
|
||||||
|
private final double remainingPulses;
|
||||||
|
// We use a double for the delay until next pulse because the Joda time API lacks precision for our calculations
|
||||||
|
private final double delayUntilNextPulse;
|
||||||
|
private final List<RateEntry> rateEntries;
|
||||||
|
|
||||||
|
public TempBasalExtraCommand(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep,
|
||||||
|
Duration programReminderInterval) {
|
||||||
|
if (rate < 0D) {
|
||||||
|
throw new IllegalArgumentException("Rate should be >= 0");
|
||||||
|
} else if (rate > OmnipodConst.MAX_BASAL_RATE) {
|
||||||
|
throw new IllegalArgumentException("Rate exceeds max basal rate");
|
||||||
|
}
|
||||||
|
if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) {
|
||||||
|
throw new IllegalArgumentException("Duration exceeds max temp basal duration");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.acknowledgementBeep = acknowledgementBeep;
|
||||||
|
this.completionBeep = completionBeep;
|
||||||
|
this.programReminderInterval = programReminderInterval;
|
||||||
|
|
||||||
|
rateEntries = RateEntry.createEntries(rate, duration);
|
||||||
|
|
||||||
|
RateEntry currentRateEntry = rateEntries.get(0);
|
||||||
|
remainingPulses = currentRateEntry.getTotalPulses();
|
||||||
|
delayUntilNextPulse = currentRateEntry.getDelayBetweenPulsesInSeconds();
|
||||||
|
|
||||||
|
encode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encode() {
|
||||||
|
byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0));
|
||||||
|
|
||||||
|
encodedData = new byte[]{
|
||||||
|
beepOptions,
|
||||||
|
(byte) 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10)));
|
||||||
|
if (remainingPulses == 0) {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100) * 10));
|
||||||
|
} else {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (RateEntry entry : rateEntries) {
|
||||||
|
encodedData = ByteUtil.concat(encodedData, entry.getRawData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.TEMP_BASAL_EXTRA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAcknowledgementBeep() {
|
||||||
|
return acknowledgementBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompletionBeep() {
|
||||||
|
return completionBeep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getProgramReminderInterval() {
|
||||||
|
return programReminderInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRemainingPulses() {
|
||||||
|
return remainingPulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDelayUntilNextPulse() {
|
||||||
|
return delayUntilNextPulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RateEntry> getRateEntries() {
|
||||||
|
return new ArrayList<>(rateEntries);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
|
||||||
|
public class ErrorResponse extends MessageBlock {
|
||||||
|
private static final int MESSAGE_LENGTH = 5;
|
||||||
|
|
||||||
|
private final ErrorResponseType errorResponseType;
|
||||||
|
private final int nonceSearchKey;
|
||||||
|
|
||||||
|
public ErrorResponse(byte[] encodedData) {
|
||||||
|
if (encodedData.length < MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
this.encodedData = ByteUtil.substring(encodedData, 2, MESSAGE_LENGTH - 2);
|
||||||
|
|
||||||
|
ErrorResponseType errorResponseType = null;
|
||||||
|
try {
|
||||||
|
errorResponseType = ErrorResponseType.fromByte(encodedData[2]);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorResponseType = errorResponseType;
|
||||||
|
this.nonceSearchKey = ByteUtil.makeUnsignedShort((int) encodedData[3], (int) encodedData[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorResponseType getErrorResponseType() {
|
||||||
|
return errorResponseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNonceSearchKey() {
|
||||||
|
return nonceSearchKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ErrorResponse{" +
|
||||||
|
"errorResponseType=" + errorResponseType +
|
||||||
|
", nonceSearchKey=" + nonceSearchKey +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class StatusResponse extends MessageBlock {
|
||||||
|
private static final int MESSAGE_LENGTH = 10;
|
||||||
|
|
||||||
|
private final DeliveryStatus deliveryStatus;
|
||||||
|
private final PodProgressStatus podProgressStatus;
|
||||||
|
private final Duration timeActive;
|
||||||
|
private final Double reservoirLevel;
|
||||||
|
private final double insulin;
|
||||||
|
private final double insulinNotDelivered;
|
||||||
|
private final byte podMessageCounter;
|
||||||
|
private final AlertSet alerts;
|
||||||
|
|
||||||
|
public StatusResponse(byte[] encodedData) {
|
||||||
|
if (encodedData.length < MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
this.encodedData = ByteUtil.substring(encodedData, 1, MESSAGE_LENGTH - 1);
|
||||||
|
|
||||||
|
this.deliveryStatus = DeliveryStatus.fromByte((byte) (ByteUtil.convertUnsignedByteToInt(encodedData[1]) >>> 4));
|
||||||
|
this.podProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[1] & 0x0F));
|
||||||
|
|
||||||
|
int minutes = ((encodedData[7] & 0x7F) << 6) | ((encodedData[8] & 0xFC) >>> 2);
|
||||||
|
this.timeActive = Duration.standardMinutes(minutes);
|
||||||
|
|
||||||
|
int highInsulinBits = (encodedData[2] & 0xF) << 9;
|
||||||
|
int middleInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[3]) << 1;
|
||||||
|
int lowInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[4]) >>> 7;
|
||||||
|
this.insulin = OmnipodConst.POD_PULSE_SIZE * (highInsulinBits | middleInsulinBits | lowInsulinBits);
|
||||||
|
this.podMessageCounter = (byte) ((encodedData[4] >>> 3) & 0xf);
|
||||||
|
|
||||||
|
this.insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * (((encodedData[4] & 0x03) << 8) | ByteUtil.convertUnsignedByteToInt(encodedData[5]));
|
||||||
|
this.alerts = new AlertSet((byte) (((encodedData[6] & 0x7f) << 1) | (ByteUtil.convertUnsignedByteToInt(encodedData[7]) >>> 7)));
|
||||||
|
|
||||||
|
double reservoirValue = (((encodedData[8] & 0x3) << 8) + ByteUtil.convertUnsignedByteToInt(encodedData[9])) * OmnipodConst.POD_PULSE_SIZE;
|
||||||
|
if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) {
|
||||||
|
reservoirLevel = null;
|
||||||
|
} else {
|
||||||
|
reservoirLevel = reservoirValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.STATUS_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeliveryStatus getDeliveryStatus() {
|
||||||
|
return deliveryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodProgressStatus getPodProgressStatus() {
|
||||||
|
return podProgressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getTimeActive() {
|
||||||
|
return timeActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getReservoirLevel() {
|
||||||
|
return reservoirLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getInsulin() {
|
||||||
|
return insulin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getInsulinNotDelivered() {
|
||||||
|
return insulinNotDelivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getPodMessageCounter() {
|
||||||
|
return podMessageCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSet getAlerts() {
|
||||||
|
return alerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
stream.write(this.getType().getValue());
|
||||||
|
stream.write(encodedData);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return stream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "StatusResponse{" +
|
||||||
|
"deliveryStatus=" + deliveryStatus +
|
||||||
|
", podProgressStatus=" + podProgressStatus +
|
||||||
|
", timeActive=" + timeActive +
|
||||||
|
", reservoirLevel=" + reservoirLevel +
|
||||||
|
", insulin=" + insulin +
|
||||||
|
", insulinNotDelivered=" + insulinNotDelivered +
|
||||||
|
", podMessageCounter=" + podMessageCounter +
|
||||||
|
", alerts=" + alerts +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
|
||||||
|
|
||||||
|
public class VersionResponse extends MessageBlock {
|
||||||
|
private final PodProgressStatus podProgressStatus;
|
||||||
|
private final FirmwareVersion pmVersion;
|
||||||
|
private final FirmwareVersion piVersion;
|
||||||
|
private final int lot;
|
||||||
|
private final int tid;
|
||||||
|
private final int address;
|
||||||
|
|
||||||
|
public VersionResponse(byte[] encodedData) {
|
||||||
|
int length = ByteUtil.convertUnsignedByteToInt(encodedData[1]) + 2;
|
||||||
|
this.encodedData = ByteUtil.substring(encodedData, 2, length - 2);
|
||||||
|
|
||||||
|
boolean extraByte;
|
||||||
|
byte[] truncatedData;
|
||||||
|
|
||||||
|
switch (length) {
|
||||||
|
case 0x17:
|
||||||
|
truncatedData = ByteUtil.substring(encodedData, 2);
|
||||||
|
extraByte = true;
|
||||||
|
break;
|
||||||
|
case 0x1D:
|
||||||
|
truncatedData = ByteUtil.substring(encodedData, 9);
|
||||||
|
extraByte = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unrecognized VersionResponse message length: " + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.podProgressStatus = PodProgressStatus.fromByte(truncatedData[7]);
|
||||||
|
this.pmVersion = new FirmwareVersion(truncatedData[0], truncatedData[1], truncatedData[2]);
|
||||||
|
this.piVersion = new FirmwareVersion(truncatedData[3], truncatedData[4], truncatedData[5]);
|
||||||
|
this.lot = ByteUtil.toInt((int) truncatedData[8], (int) truncatedData[9],
|
||||||
|
(int) truncatedData[10], (int) truncatedData[11], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
this.tid = ByteUtil.toInt((int) truncatedData[12], (int) truncatedData[13],
|
||||||
|
(int) truncatedData[14], (int) truncatedData[15], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
|
||||||
|
int indexIncrementor = extraByte ? 1 : 0;
|
||||||
|
|
||||||
|
this.address = ByteUtil.toInt((int) truncatedData[16 + indexIncrementor], (int) truncatedData[17 + indexIncrementor],
|
||||||
|
(int) truncatedData[18 + indexIncrementor], (int) truncatedData[19 + indexIncrementor], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.VERSION_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodProgressStatus getPodProgressStatus() {
|
||||||
|
return podProgressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVersion getPmVersion() {
|
||||||
|
return pmVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVersion getPiVersion() {
|
||||||
|
return piVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLot() {
|
||||||
|
return lot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTid() {
|
||||||
|
return tid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VersionResponse{" +
|
||||||
|
"podProgressStatus=" + podProgressStatus +
|
||||||
|
", pmVersion=" + pmVersion +
|
||||||
|
", piVersion=" + piVersion +
|
||||||
|
", lot=" + lot +
|
||||||
|
", tid=" + tid +
|
||||||
|
", address=" + address +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public abstract class PodInfo {
|
||||||
|
private final byte[] encodedData;
|
||||||
|
|
||||||
|
public PodInfo(byte[] encodedData) {
|
||||||
|
this.encodedData = encodedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract PodInfoType getType();
|
||||||
|
|
||||||
|
public byte[] getEncodedData() {
|
||||||
|
return encodedData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class PodInfoActiveAlerts extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 11;
|
||||||
|
|
||||||
|
private final byte[] word278; // Unknown use
|
||||||
|
private final List<AlertActivation> alertActivations;
|
||||||
|
|
||||||
|
public PodInfoActiveAlerts(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
word278 = ByteUtil.substring(encodedData, 1, 2);
|
||||||
|
|
||||||
|
alertActivations = new ArrayList<>();
|
||||||
|
|
||||||
|
for (AlertSlot alertSlot : AlertSlot.values()) {
|
||||||
|
int valueHighBits = ByteUtil.convertUnsignedByteToInt(encodedData[3 + alertSlot.getValue() * 2]);
|
||||||
|
int valueLowBits = ByteUtil.convertUnsignedByteToInt(encodedData[4 + alertSlot.getValue() * 2]);
|
||||||
|
int value = (valueHighBits << 8) | valueLowBits;
|
||||||
|
if (value != 0) {
|
||||||
|
alertActivations.add(new AlertActivation(alertSlot, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.ACTIVE_ALERTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getWord278() {
|
||||||
|
return word278;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AlertActivation> getAlertActivations() {
|
||||||
|
return new ArrayList<>(alertActivations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoActiveAlerts{" +
|
||||||
|
"word278=" + Arrays.toString(word278) +
|
||||||
|
", alertActivations=" + alertActivations +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AlertActivation {
|
||||||
|
private final AlertSlot alertSlot;
|
||||||
|
private final int value;
|
||||||
|
|
||||||
|
private AlertActivation(AlertSlot alertSlot, int value) {
|
||||||
|
this.alertSlot = alertSlot;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValueAsUnits() {
|
||||||
|
return value * OmnipodConst.POD_PULSE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getValueAsDuration() {
|
||||||
|
return Duration.standardMinutes(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSlot getAlertSlot() {
|
||||||
|
return alertSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AlertActivation{" +
|
||||||
|
"alertSlot=" + alertSlot +
|
||||||
|
", valueAsUnits=" + getValueAsUnits() +
|
||||||
|
", valueAsDuration=" + getValueAsDuration() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventCode;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoDataLog extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 8;
|
||||||
|
private final FaultEventCode faultEventCode;
|
||||||
|
private final Duration timeFaultEvent;
|
||||||
|
private final Duration timeSinceActivation;
|
||||||
|
private final byte dataChunkSize;
|
||||||
|
private final byte maximumNumberOfDwords;
|
||||||
|
private final List<byte[]> dwords;
|
||||||
|
|
||||||
|
public PodInfoDataLog(byte[] encodedData, int bodyLength) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
faultEventCode = FaultEventCode.fromByte(encodedData[1]);
|
||||||
|
timeFaultEvent = Duration.standardMinutes(ByteUtil.toInt(encodedData[2], encodedData[3]));
|
||||||
|
timeSinceActivation = Duration.standardMinutes(ByteUtil.toInt(encodedData[4], encodedData[5]));
|
||||||
|
dataChunkSize = encodedData[6];
|
||||||
|
maximumNumberOfDwords = encodedData[7];
|
||||||
|
|
||||||
|
dwords = new ArrayList<>();
|
||||||
|
|
||||||
|
int numberOfDwords = (bodyLength - 8) / 4;
|
||||||
|
for (int i = 0; i < numberOfDwords; i++) {
|
||||||
|
dwords.add(ByteUtil.substring(encodedData, 8 + (4 * i), 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.DATA_LOG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FaultEventCode getFaultEventCode() {
|
||||||
|
return faultEventCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getTimeFaultEvent() {
|
||||||
|
return timeFaultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getTimeSinceActivation() {
|
||||||
|
return timeSinceActivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getDataChunkSize() {
|
||||||
|
return dataChunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getMaximumNumberOfDwords() {
|
||||||
|
return maximumNumberOfDwords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<byte[]> getDwords() {
|
||||||
|
return Collections.unmodifiableList(dwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoDataLog{" +
|
||||||
|
"faultEventCode=" + faultEventCode +
|
||||||
|
", timeFaultEvent=" + timeFaultEvent +
|
||||||
|
", timeSinceActivation=" + timeSinceActivation +
|
||||||
|
", dataChunkSize=" + dataChunkSize +
|
||||||
|
", maximumNumberOfDwords=" + maximumNumberOfDwords +
|
||||||
|
", dwords=" + dwords +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventCode;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoFaultAndInitializationTime extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 17;
|
||||||
|
private final FaultEventCode faultEventCode;
|
||||||
|
private final Duration timeFaultEvent;
|
||||||
|
private final DateTime initializationTime;
|
||||||
|
|
||||||
|
public PodInfoFaultAndInitializationTime(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
faultEventCode = FaultEventCode.fromByte(encodedData[1]);
|
||||||
|
timeFaultEvent = Duration.standardMinutes(((encodedData[2] & 0b1) << 8) + encodedData[3]);
|
||||||
|
// We ignore time zones here because we don't keep the time zone in which the pod was initially set up
|
||||||
|
// Which is fine because we don't use the initialization time for anything important anyway
|
||||||
|
initializationTime = new DateTime(2000 + encodedData[14], encodedData[12], encodedData[13], encodedData[15], encodedData[16]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.FAULT_AND_INITIALIZATION_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FaultEventCode getFaultEventCode() {
|
||||||
|
return faultEventCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getTimeFaultEvent() {
|
||||||
|
return timeFaultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getInitializationTime() {
|
||||||
|
return initializationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoFaultAndInitializationTime{" +
|
||||||
|
"faultEventCode=" + faultEventCode +
|
||||||
|
", timeFaultEvent=" + timeFaultEvent +
|
||||||
|
", initializationTime=" + initializationTime +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventCode;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.LogEventErrorCode;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class PodInfoFaultEvent extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 21;
|
||||||
|
|
||||||
|
private final PodProgressStatus podProgressStatus;
|
||||||
|
private final DeliveryStatus deliveryStatus;
|
||||||
|
private final double insulinNotDelivered;
|
||||||
|
private final byte podMessageCounter;
|
||||||
|
private final double totalInsulinDelivered;
|
||||||
|
private final FaultEventCode faultEventCode;
|
||||||
|
private final Duration faultEventTime;
|
||||||
|
private final Double reservoirLevel;
|
||||||
|
private final Duration timeSinceActivation;
|
||||||
|
private final AlertSet unacknowledgedAlerts;
|
||||||
|
private final boolean faultAccessingTables;
|
||||||
|
private final LogEventErrorCode logEventErrorType;
|
||||||
|
private final PodProgressStatus logEventErrorPodProgressStatus;
|
||||||
|
private final byte receiverLowGain;
|
||||||
|
private final byte radioRSSI;
|
||||||
|
private final PodProgressStatus podProgressStatusAtTimeOfFirstLoggedFaultEvent;
|
||||||
|
private final byte[] unknownValue;
|
||||||
|
|
||||||
|
public PodInfoFaultEvent(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
podProgressStatus = PodProgressStatus.fromByte(encodedData[1]);
|
||||||
|
deliveryStatus = DeliveryStatus.fromByte(encodedData[2]);
|
||||||
|
insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[3], encodedData[4]);
|
||||||
|
podMessageCounter = encodedData[5];
|
||||||
|
totalInsulinDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[6], encodedData[7]);
|
||||||
|
faultEventCode = FaultEventCode.fromByte(encodedData[8]);
|
||||||
|
|
||||||
|
int minutesSinceActivation = ByteUtil.toInt(encodedData[9], encodedData[10]);
|
||||||
|
if (minutesSinceActivation == 0xffff) {
|
||||||
|
faultEventTime = null;
|
||||||
|
} else {
|
||||||
|
faultEventTime = Duration.standardMinutes(minutesSinceActivation);
|
||||||
|
}
|
||||||
|
|
||||||
|
double reservoirValue = ((encodedData[11] & 0x03) << 8) +
|
||||||
|
ByteUtil.convertUnsignedByteToInt(encodedData[12]) * OmnipodConst.POD_PULSE_SIZE;
|
||||||
|
if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) {
|
||||||
|
reservoirLevel = null;
|
||||||
|
} else {
|
||||||
|
reservoirLevel = reservoirValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutesActive = ByteUtil.toInt(encodedData[13], encodedData[14]);
|
||||||
|
timeSinceActivation = Duration.standardMinutes(minutesActive);
|
||||||
|
|
||||||
|
unacknowledgedAlerts = new AlertSet(encodedData[15]);
|
||||||
|
faultAccessingTables = encodedData[16] == 0x02;
|
||||||
|
logEventErrorType = LogEventErrorCode.fromByte((byte) (encodedData[17] >>> 4));
|
||||||
|
logEventErrorPodProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[17] & 0x0f));
|
||||||
|
receiverLowGain = (byte) (ByteUtil.convertUnsignedByteToInt(encodedData[18]) >>> 6);
|
||||||
|
radioRSSI = (byte) (encodedData[18] & 0x3f);
|
||||||
|
podProgressStatusAtTimeOfFirstLoggedFaultEvent = PodProgressStatus.fromByte((byte) (encodedData[19] & 0x0f));
|
||||||
|
unknownValue = ByteUtil.substring(encodedData, 20, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.FAULT_EVENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodProgressStatus getPodProgressStatus() {
|
||||||
|
return podProgressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeliveryStatus getDeliveryStatus() {
|
||||||
|
return deliveryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getInsulinNotDelivered() {
|
||||||
|
return insulinNotDelivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getPodMessageCounter() {
|
||||||
|
return podMessageCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalInsulinDelivered() {
|
||||||
|
return totalInsulinDelivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FaultEventCode getFaultEventCode() {
|
||||||
|
return faultEventCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getFaultEventTime() {
|
||||||
|
return faultEventTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getReservoirLevel() {
|
||||||
|
return reservoirLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getTimeSinceActivation() {
|
||||||
|
return timeSinceActivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSet getUnacknowledgedAlerts() {
|
||||||
|
return unacknowledgedAlerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFaultAccessingTables() {
|
||||||
|
return faultAccessingTables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogEventErrorCode getLogEventErrorType() {
|
||||||
|
return logEventErrorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodProgressStatus getLogEventErrorPodProgressStatus() {
|
||||||
|
return logEventErrorPodProgressStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getReceiverLowGain() {
|
||||||
|
return receiverLowGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getRadioRSSI() {
|
||||||
|
return radioRSSI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodProgressStatus getPodProgressStatusAtTimeOfFirstLoggedFaultEvent() {
|
||||||
|
return podProgressStatusAtTimeOfFirstLoggedFaultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getUnknownValue() {
|
||||||
|
return unknownValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoFaultEvent{" +
|
||||||
|
"podProgressStatus=" + podProgressStatus +
|
||||||
|
", deliveryStatus=" + deliveryStatus +
|
||||||
|
", insulinNotDelivered=" + insulinNotDelivered +
|
||||||
|
", podMessageCounter=" + podMessageCounter +
|
||||||
|
", totalInsulinDelivered=" + totalInsulinDelivered +
|
||||||
|
", faultEventCode=" + faultEventCode +
|
||||||
|
", faultEventTime=" + faultEventTime +
|
||||||
|
", reservoirLevel=" + reservoirLevel +
|
||||||
|
", timeSinceActivation=" + timeSinceActivation +
|
||||||
|
", unacknowledgedAlerts=" + unacknowledgedAlerts +
|
||||||
|
", faultAccessingTables=" + faultAccessingTables +
|
||||||
|
", logEventErrorType=" + logEventErrorType +
|
||||||
|
", logEventErrorPodProgressStatus=" + logEventErrorPodProgressStatus +
|
||||||
|
", receiverLowGain=" + receiverLowGain +
|
||||||
|
", radioRSSI=" + radioRSSI +
|
||||||
|
", podProgressStatusAtTimeOfFirstLoggedFaultEvent=" + podProgressStatusAtTimeOfFirstLoggedFaultEvent +
|
||||||
|
", unknownValue=" + ByteUtil.shortHexString(unknownValue) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoLowFlashLogDump extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 8;
|
||||||
|
|
||||||
|
private final byte numberOfBytes;
|
||||||
|
private final byte[] dataFromFlashMemory;
|
||||||
|
private final int podAddress;
|
||||||
|
|
||||||
|
public PodInfoLowFlashLogDump(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfBytes = encodedData[2];
|
||||||
|
podAddress = ByteUtil.toInt((int) encodedData[3], (int) encodedData[4], (int) encodedData[5], (int) encodedData[6], ByteUtil.BitConversion.BIG_ENDIAN);
|
||||||
|
dataFromFlashMemory = ByteUtil.substring(encodedData, 3, ByteUtil.convertUnsignedByteToInt(encodedData[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.LOW_FLASH_DUMP_LOG;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getNumberOfBytes() {
|
||||||
|
return numberOfBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getDataFromFlashMemory() {
|
||||||
|
return dataFromFlashMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPodAddress() {
|
||||||
|
return podAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoLowFlashLogDump{" +
|
||||||
|
"numberOfBytes=" + numberOfBytes +
|
||||||
|
", dataFromFlashMemory=" + ByteUtil.shortHexString(dataFromFlashMemory) +
|
||||||
|
", podAddress=" + podAddress +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoOlderHighFlashLogDump extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 3;
|
||||||
|
|
||||||
|
private final ArrayList<byte[]> dwords;
|
||||||
|
|
||||||
|
public PodInfoOlderHighFlashLogDump(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
dwords = new ArrayList<>();
|
||||||
|
|
||||||
|
int numberOfDwordLogEntries = ByteUtil.toInt(encodedData[1], encodedData[2]);
|
||||||
|
for (int i = 0; numberOfDwordLogEntries > i; i++) {
|
||||||
|
byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4);
|
||||||
|
dwords.add(dword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.OLDER_HIGH_FLASH_LOG_DUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<byte[]> getDwords() {
|
||||||
|
return Collections.unmodifiableList(dwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoOlderHighFlashLogDump{" +
|
||||||
|
"dwords=" + dwords +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoRecentHighFlashLogDump extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 3;
|
||||||
|
|
||||||
|
private final ArrayList<byte[]> dwords;
|
||||||
|
|
||||||
|
private final int lastEntryIndex;
|
||||||
|
|
||||||
|
public PodInfoRecentHighFlashLogDump(byte[] encodedData, int bodyLength) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
lastEntryIndex = ByteUtil.toInt(encodedData[1], encodedData[2]);
|
||||||
|
dwords = new ArrayList<>();
|
||||||
|
|
||||||
|
int numberOfDwords = (bodyLength - 3) / 4;
|
||||||
|
|
||||||
|
for (int i = 0; numberOfDwords > i; i++) {
|
||||||
|
byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4);
|
||||||
|
dwords.add(dword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.RECENT_HIGH_FLASH_LOG_DUMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<byte[]> getDwords() {
|
||||||
|
return Collections.unmodifiableList(dwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastEntryIndex() {
|
||||||
|
return lastEntryIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoRecentHighFlashLogDump{" +
|
||||||
|
"lastEntryIndex=" + lastEntryIndex +
|
||||||
|
",dwords=" + dwords +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoResponse extends MessageBlock {
|
||||||
|
private final PodInfoType subType;
|
||||||
|
private final PodInfo podInfo;
|
||||||
|
|
||||||
|
public PodInfoResponse(byte[] encodedData) {
|
||||||
|
int bodyLength = ByteUtil.convertUnsignedByteToInt(encodedData[1]);
|
||||||
|
|
||||||
|
this.encodedData = ByteUtil.substring(encodedData, 2, bodyLength);
|
||||||
|
subType = PodInfoType.fromByte(encodedData[2]);
|
||||||
|
podInfo = subType.decode(this.encodedData, bodyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodInfoType getSubType() {
|
||||||
|
return subType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends PodInfo> T getPodInfo() {
|
||||||
|
return (T) podInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBlockType getType() {
|
||||||
|
return MessageBlockType.POD_INFO_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoResponse{" +
|
||||||
|
"subType=" + subType.name() +
|
||||||
|
", podInfo=" + podInfo.toString() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType;
|
||||||
|
|
||||||
|
public class PodInfoTestValues extends PodInfo {
|
||||||
|
private static final int MINIMUM_MESSAGE_LENGTH = 5;
|
||||||
|
private final byte byte1;
|
||||||
|
private final byte byte2;
|
||||||
|
private final byte byte3;
|
||||||
|
private final byte byte4;
|
||||||
|
|
||||||
|
public PodInfoTestValues(byte[] encodedData) {
|
||||||
|
super(encodedData);
|
||||||
|
|
||||||
|
if (encodedData.length < MINIMUM_MESSAGE_LENGTH) {
|
||||||
|
throw new IllegalArgumentException("Not enough data");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte1 = encodedData[1];
|
||||||
|
byte2 = encodedData[2];
|
||||||
|
byte3 = encodedData[3];
|
||||||
|
byte4 = encodedData[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PodInfoType getType() {
|
||||||
|
return PodInfoType.HARDCODED_TEST_VALUES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte1() {
|
||||||
|
return byte1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte2() {
|
||||||
|
return byte2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte3() {
|
||||||
|
return byte3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getByte4() {
|
||||||
|
return byte4;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodInfoTestValues{" +
|
||||||
|
"byte1=" + byte1 +
|
||||||
|
", byte2=" + byte2 +
|
||||||
|
", byte3=" + byte3 +
|
||||||
|
", byte4=" + byte4 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class AlertConfiguration {
|
||||||
|
private final AlertType alertType;
|
||||||
|
private final AlertSlot alertSlot;
|
||||||
|
private final boolean active;
|
||||||
|
private final boolean autoOffModifier;
|
||||||
|
private final Duration duration;
|
||||||
|
private final AlertTrigger<?> alertTrigger;
|
||||||
|
private final BeepRepeat beepRepeat;
|
||||||
|
private final BeepType beepType;
|
||||||
|
|
||||||
|
public AlertConfiguration(AlertType alertType, AlertSlot alertSlot, boolean active, boolean autoOffModifier,
|
||||||
|
Duration duration, AlertTrigger alertTrigger,
|
||||||
|
BeepType beepType, BeepRepeat beepRepeat) {
|
||||||
|
this.alertType = alertType;
|
||||||
|
this.alertSlot = alertSlot;
|
||||||
|
this.active = active;
|
||||||
|
this.autoOffModifier = autoOffModifier;
|
||||||
|
this.duration = duration;
|
||||||
|
this.alertTrigger = alertTrigger;
|
||||||
|
this.beepRepeat = beepRepeat;
|
||||||
|
this.beepType = beepType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertType getAlertType() {
|
||||||
|
return alertType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSlot getAlertSlot() {
|
||||||
|
return alertSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
int firstByte = (alertSlot.getValue() << 4);
|
||||||
|
firstByte += active ? (1 << 3) : 0;
|
||||||
|
|
||||||
|
if (alertTrigger instanceof UnitsRemainingAlertTrigger) {
|
||||||
|
firstByte += 1 << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoOffModifier) {
|
||||||
|
firstByte += 1 << 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstByte += ((int) duration.getStandardMinutes() >>> 8) & 0x1;
|
||||||
|
|
||||||
|
byte[] encodedData = new byte[]{
|
||||||
|
(byte) firstByte,
|
||||||
|
(byte) duration.getStandardMinutes()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (alertTrigger instanceof UnitsRemainingAlertTrigger) {
|
||||||
|
int ticks = (int) (((UnitsRemainingAlertTrigger) alertTrigger).getValue() / OmnipodConst.POD_PULSE_SIZE / 2);
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(ticks));
|
||||||
|
} else if (alertTrigger instanceof TimerAlertTrigger) {
|
||||||
|
int durationInMinutes = (int) ((TimerAlertTrigger) alertTrigger).getValue().getStandardMinutes();
|
||||||
|
encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(durationInMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedData = ByteUtil.concat(encodedData, beepType.getValue());
|
||||||
|
encodedData = ByteUtil.concat(encodedData, beepRepeat.getValue());
|
||||||
|
|
||||||
|
return encodedData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
public class AlertConfigurationFactory {
|
||||||
|
public static AlertConfiguration createExpirationAdvisoryAlertConfiguration(Duration timeUntilAlert, Duration duration) {
|
||||||
|
return new AlertConfiguration(AlertType.EXPIRATION_ADVISORY_ALERT, AlertSlot.SLOT7, true, false, duration,
|
||||||
|
new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_60_MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertConfiguration createShutdownImminentAlertConfiguration(Duration timeUntilAlert) {
|
||||||
|
return new AlertConfiguration(AlertType.SHUTDOWN_IMMINENT_ALARM, AlertSlot.SLOT2, true, false, Duration.ZERO,
|
||||||
|
new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_15_MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertConfiguration createAutoOffAlertConfiguration(boolean active, Duration countdownDuration) {
|
||||||
|
return new AlertConfiguration(AlertType.AUTO_OFF_ALARM, AlertSlot.SLOT0, active, true,
|
||||||
|
Duration.standardMinutes(15), new TimerAlertTrigger(countdownDuration),
|
||||||
|
BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_15_MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertConfiguration createFinishSetupReminderAlertConfiguration() {
|
||||||
|
return new AlertConfiguration(AlertType.FINISH_SETUP_REMINDER, AlertSlot.SLOT7, true, false,
|
||||||
|
Duration.standardMinutes(55), new TimerAlertTrigger(Duration.standardMinutes(5)),
|
||||||
|
BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_5_MINUTES);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AlertSet {
|
||||||
|
private final List<AlertSlot> alertSlots;
|
||||||
|
|
||||||
|
public AlertSet(byte rawValue) {
|
||||||
|
alertSlots = new ArrayList<>();
|
||||||
|
for (AlertSlot alertSlot : AlertSlot.values()) {
|
||||||
|
if ((alertSlot.getBitMaskValue() & rawValue) != 0) {
|
||||||
|
alertSlots.add(alertSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSet(List<AlertSlot> alertSlots) {
|
||||||
|
this.alertSlots = alertSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AlertSlot> getAlertSlots() {
|
||||||
|
return new ArrayList<>(alertSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return alertSlots.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getRawValue() {
|
||||||
|
byte value = 0;
|
||||||
|
for (AlertSlot alertSlot : alertSlots) {
|
||||||
|
value |= alertSlot.getBitMaskValue();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AlertSet{" +
|
||||||
|
"alertSlots=" + alertSlots +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum AlertSlot {
|
||||||
|
SLOT0((byte) 0x00),
|
||||||
|
SLOT1((byte) 0x01),
|
||||||
|
SLOT2((byte) 0x02),
|
||||||
|
SLOT3((byte) 0x03),
|
||||||
|
SLOT4((byte) 0x04),
|
||||||
|
SLOT5((byte) 0x05),
|
||||||
|
SLOT6((byte) 0x06),
|
||||||
|
SLOT7((byte) 0x07);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
AlertSlot(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertSlot fromByte(byte value) {
|
||||||
|
for (AlertSlot type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown AlertSlot: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getBitMaskValue() {
|
||||||
|
return (byte) (1 << value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public abstract class AlertTrigger<T> {
|
||||||
|
protected T value;
|
||||||
|
|
||||||
|
public AlertTrigger(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum AlertType {
|
||||||
|
WAITING_FOR_PAIRING_REMINDER,
|
||||||
|
FINISH_SETUP_REMINDER,
|
||||||
|
EXPIRATION_ALERT,
|
||||||
|
EXPIRATION_ADVISORY_ALERT,
|
||||||
|
SHUTDOWN_IMMINENT_ALARM,
|
||||||
|
LOW_RESERVOIC_ALERT,
|
||||||
|
AUTO_OFF_ALARM
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum BeepRepeat {
|
||||||
|
ONCE((byte) 0x00),
|
||||||
|
EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_60_MINUTES((byte) 0x01),
|
||||||
|
EVERY_MINUTE_FOR_15_MINUTES((byte) 0x02),
|
||||||
|
EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES((byte) 0x03),
|
||||||
|
EVERY_3_MINUTES_DELAYED((byte) 0x04),
|
||||||
|
EVERY_60_MINUTES((byte) 0x05),
|
||||||
|
EVERY_15_MINUTES((byte) 0x06),
|
||||||
|
EVERY_15_MINUTES_DELAYED((byte) 0x07),
|
||||||
|
EVERY_5_MINUTES((byte) 0x08);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
BeepRepeat(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum BeepType {
|
||||||
|
NO_BEEP((byte) 0x00),
|
||||||
|
BEEP_BEEP_BEEP_BEEP((byte) 0x01),
|
||||||
|
BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP((byte) 0x02),
|
||||||
|
BIP_BIP((byte) 0x03),
|
||||||
|
BEEP((byte) 0x04),
|
||||||
|
BEEP_BEEP_BEEP((byte) 0x05),
|
||||||
|
BEEEEEEP((byte) 0x06),
|
||||||
|
BIP_BIP_BIP_BIP_BIP_BIP((byte) 0x07),
|
||||||
|
BEEEP_BEEEP((byte) 0x08),
|
||||||
|
BEEP_BEEP((byte) 0xB),
|
||||||
|
BEEEP((byte) 0xC),
|
||||||
|
BIP_BEEEEEP((byte) 0xD),
|
||||||
|
FIVE_SECONDS_BEEP((byte) 0xE),
|
||||||
|
BEEP_CONFIG_NO_BEEP((byte) 0xF);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
BeepType(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeepType fromByte(byte value) {
|
||||||
|
for (BeepType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown BeepType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum DeliveryStatus {
|
||||||
|
SUSPENDED((byte) 0x00),
|
||||||
|
NORMAL((byte) 0x01),
|
||||||
|
TEMP_BASAL_RUNNING((byte) 0x02),
|
||||||
|
PRIMING((byte) 0x04),
|
||||||
|
BOLUS_IN_PROGRESS((byte) 0x05),
|
||||||
|
BOLUS_AND_TEMP_BASAL((byte) 0x06);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
DeliveryStatus(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeliveryStatus fromByte(byte value) {
|
||||||
|
for (DeliveryStatus type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown DeliveryStatus: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum DeliveryType {
|
||||||
|
NONE((byte) 0x00),
|
||||||
|
BASAL((byte) 0x01),
|
||||||
|
TEMP_BASAL((byte) 0x02),
|
||||||
|
BOLUS((byte) 0x04),
|
||||||
|
EXTENDED_BOLUS((byte) 0x08);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
DeliveryType(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeliveryType fromByte(byte value) {
|
||||||
|
for (DeliveryType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown DeliveryType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum ErrorResponseType {
|
||||||
|
BAD_NONCE((byte) 0x14);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
ErrorResponseType(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorResponseType fromByte(byte value) {
|
||||||
|
for (ErrorResponseType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown ErrorResponseType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum FaultEventCode {
|
||||||
|
NO_FAULTS((byte) 0x00),
|
||||||
|
FAILED_FLASH_ERASE((byte) 0x01),
|
||||||
|
FAILED_FLASH_STORE((byte) 0x02),
|
||||||
|
TABLE_CORRUPTION_BASAL_SUBCOMMAND((byte) 0x03),
|
||||||
|
CORRUPTION_BYTE_720((byte) 0x05),
|
||||||
|
DATA_CORRUPTION_IN_TEST_RTC_INTERRUPT((byte) 0x06),
|
||||||
|
RTC_INTERRUPT_HANDLER_INCONSISTENT_STATE((byte) 0x07),
|
||||||
|
VALUE_GREATER_THAN_8((byte) 0x08),
|
||||||
|
BF_0_NOT_EQUAL_TO_BF_1((byte) 0x0A),
|
||||||
|
TABLE_CORRUPTION_TEMP_BASAL_SUBCOMMAND((byte) 0x0B),
|
||||||
|
RESET_DUE_TO_COP((byte) 0x0D),
|
||||||
|
RESET_DUE_TO_ILLEGAL_OPCODE((byte) 0x0E),
|
||||||
|
RESET_DUE_TO_ILLEGAL_ADDRESS((byte) 0x0F),
|
||||||
|
RESET_DUE_TO_SAWCOP((byte) 0x10),
|
||||||
|
CORRUPTION_IN_BYTE_866((byte) 0x11),
|
||||||
|
RESET_DUE_TO_LVD((byte) 0x12),
|
||||||
|
MESSAGE_LENGTH_TOO_LONG((byte) 0x13),
|
||||||
|
OCCLUDED((byte) 0x14),
|
||||||
|
CORRUPTION_IN_WORD_129((byte) 0x15),
|
||||||
|
CORRUPTION_IN_BYTE_868((byte) 0x16),
|
||||||
|
CORRUPTION_IN_A_VALIDATED_TABLE((byte) 0x17),
|
||||||
|
RESERVOIR_EMPTY((byte) 0x18),
|
||||||
|
BAD_POWER_SWITCH_ARRAY_VALUE_1((byte) 0x19),
|
||||||
|
BAD_POWER_SWITCH_ARRAY_VALUE_2((byte) 0x1A),
|
||||||
|
BAD_LOAD_CNTH_VALUE((byte) 0x1B),
|
||||||
|
EXCEEDED_MAXIMUM_POD_LIFE_80_HRS((byte) 0x1C),
|
||||||
|
BAD_STATE_COMMAND_1_A_SCHEDULE_PARSE((byte) 0x1D),
|
||||||
|
UNEXPECTED_STATE_IN_REGISTER_UPON_RESET((byte) 0x1E),
|
||||||
|
WRONG_SUMMARY_FOR_TABLE_129((byte) 0x1F),
|
||||||
|
VALIDATE_COUNT_ERROR_WHEN_BOLUSING((byte) 0x20),
|
||||||
|
BAD_TIMER_VARIABLE_STATE((byte) 0x21),
|
||||||
|
UNEXPECTED_RTC_MODULE_VALUE_DURING_RESET((byte) 0x22),
|
||||||
|
PROBLEM_CALIBRATE_TIMER((byte) 0x23),
|
||||||
|
RTC_INTERRUPT_HANDLER_UNEXPECTED_CALL((byte) 0x26),
|
||||||
|
MISSING_2_HOUR_ALERT_TO_FILL_TANK((byte) 0x27),
|
||||||
|
FAULT_EVENT_SETUP_POD((byte) 0x28),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_0((byte) 0x29),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_1((byte) 0x2A),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_2((byte) 0x2B),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_3((byte) 0x2C),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_4((byte) 0x2D),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_5((byte) 0x2E),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_6((byte) 0x2F),
|
||||||
|
ERROR_MAIN_LOOP_HELPER_7((byte) 0x30),
|
||||||
|
INSULIN_DELIVERY_COMMAND_ERROR((byte) 0x31),
|
||||||
|
BAD_VALUE_STARTUP_TEST((byte) 0x32),
|
||||||
|
CONNECTED_POD_COMMAND_TIMEOUT((byte) 0x33),
|
||||||
|
RESET_FROM_UNKNOWN_CAUSE((byte) 0x34),
|
||||||
|
ERROR_FLASH_INITIALIZATION((byte) 0x36),
|
||||||
|
BAD_PIEZO_VALUE((byte) 0x37),
|
||||||
|
UNEXPECTED_VALUE_BYTE_358((byte) 0x38),
|
||||||
|
PROBLEM_WITH_LOAD_1_AND_2((byte) 0x39),
|
||||||
|
A_GREATER_THAN_7_IN_MESSAGE((byte) 0x3A),
|
||||||
|
FAILED_TEST_SAW_RESET((byte) 0x3B),
|
||||||
|
TEST_IN_PROGRESS((byte) 0x3C),
|
||||||
|
PROBLEM_WITH_PUMP_ANCHOR((byte) 0x3D),
|
||||||
|
ERROR_FLASH_WRITE((byte) 0x3E),
|
||||||
|
ENCODER_COUNT_TOO_HIGH((byte) 0x40),
|
||||||
|
ENCODER_COUNT_EXCESSIVE_VARIANCE((byte) 0x41),
|
||||||
|
ENCODER_COUNT_TOO_LOW((byte) 0x42),
|
||||||
|
ENCODER_COUNT_PROBLEM((byte) 0x43),
|
||||||
|
CHECK_VOLTAGE_OPEN_WIRE_1((byte) 0x44),
|
||||||
|
CHECK_VOLTAGE_OPEN_WIRE_2((byte) 0x45),
|
||||||
|
PROBLEM_WITH_LOAD_1_AND_2_TYPE_46((byte) 0x46),
|
||||||
|
PROBLEM_WITH_LOAD_1_AND_2_TYPE_47((byte) 0x47),
|
||||||
|
BAD_TIMER_CALIBRATION((byte) 0x48),
|
||||||
|
BAD_TIMER_RATIOS((byte) 0x49),
|
||||||
|
BAD_TIMER_VALUES((byte) 0x4A),
|
||||||
|
TRIM_ICS_TOO_CLOSE_TO_0_X_1_FF((byte) 0x4B),
|
||||||
|
PROBLEM_FINDING_BEST_TRIM_VALUE((byte) 0x4C),
|
||||||
|
BAD_SET_TPM_1_MULTI_CASES_VALUE((byte) 0x4D),
|
||||||
|
UNEXPECTED_RF_ERROR_FLAG_DURING_RESET((byte) 0x4F),
|
||||||
|
BAD_CHECK_SDRH_AND_BYTE_11_F_STATE((byte) 0x51),
|
||||||
|
ISSUE_TXO_KPROCESS_INPUT_BUFFER((byte) 0x52),
|
||||||
|
WRONG_VALUE_WORD_107((byte) 0x53),
|
||||||
|
PACKET_FRAME_LENGTH_TOO_LONG((byte) 0x54),
|
||||||
|
UNEXPECTED_IRQ_HIGHIN_TIMER_TICK((byte) 0x55),
|
||||||
|
UNEXPECTED_IRQ_LOWIN_TIMER_TICK((byte) 0x56),
|
||||||
|
BAD_ARG_TO_GET_ENTRY((byte) 0x57),
|
||||||
|
BAD_ARG_TO_UPDATE_37_A_TABLE((byte) 0x58),
|
||||||
|
ERROR_UPDATING_37_A_TABLE((byte) 0x59),
|
||||||
|
OCCLUSION_CHECK_VALUE_TOO_HIGH((byte) 0x5A),
|
||||||
|
LOAD_TABLE_CORRUPTION((byte) 0x5B),
|
||||||
|
PRIME_OPEN_COUNT_TOO_LOW((byte) 0x5C),
|
||||||
|
BAD_VALUE_BYTE_109((byte) 0x5D),
|
||||||
|
DISABLE_FLASH_SECURITY_FAILED((byte) 0x5E),
|
||||||
|
CHECK_VOLTAGE_FAILURE((byte) 0x5F),
|
||||||
|
OCCLUSION_CHECK_STARTUP_1((byte) 0x60),
|
||||||
|
OCCLUSION_CHECK_STARTUP_2((byte) 0x61),
|
||||||
|
OCCLUSION_CHECK_TIMEOUTS_1((byte) 0x62),
|
||||||
|
OCCLUSION_CHECK_TIMEOUTS_2((byte) 0x66),
|
||||||
|
OCCLUSION_CHECK_TIMEOUTS_3((byte) 0x67),
|
||||||
|
OCCLUSION_CHECK_PULSE_ISSUE((byte) 0x68),
|
||||||
|
OCCLUSION_CHECK_BOLUS_PROBLEM((byte) 0x69),
|
||||||
|
OCCLUSION_CHECK_ABOVE_THRESHOLD((byte) 0x6A),
|
||||||
|
BASAL_UNDER_INFUSION((byte) 0x80),
|
||||||
|
BASAL_OVER_INFUSION((byte) 0x81),
|
||||||
|
TEMP_BASAL_UNDER_INFUSION((byte) 0x82),
|
||||||
|
TEMP_BASAL_OVER_INFUSION((byte) 0x83),
|
||||||
|
BOLUS_UNDER_INFUSION((byte) 0x84),
|
||||||
|
BOLUS_OVER_INFUSION((byte) 0x85),
|
||||||
|
BASAL_OVER_INFUSION_PULSE((byte) 0x86),
|
||||||
|
TEMP_BASAL_OVER_INFUSION_PULSE((byte) 0x87),
|
||||||
|
BOLUS_OVER_INFUSION_PULSE((byte) 0x88),
|
||||||
|
IMMEDIATE_BOLUS_OVER_INFUSION_PULSE((byte) 0x89),
|
||||||
|
EXTENDED_BOLUS_OVER_INFUSION_PULSE((byte) 0x8A),
|
||||||
|
CORRUPTION_OF_TABLES((byte) 0x8B),
|
||||||
|
BAD_INPUT_TO_VERIFY_AND_START_PUMP((byte) 0x8D),
|
||||||
|
BAD_PUMP_REQ_5_STATE((byte) 0x8E),
|
||||||
|
COMMAND_1_A_PARSE_UNEXPECTED_FAILED((byte) 0x8F),
|
||||||
|
BAD_VALUE_FOR_TABLES((byte) 0x90),
|
||||||
|
BAD_PUMP_REQ_1_STATE((byte) 0x91),
|
||||||
|
BAD_PUMP_REQ_2_STATE((byte) 0x92),
|
||||||
|
BAD_PUMP_REQ_3_STATE((byte) 0x93),
|
||||||
|
BAD_VALUE_FIELD_6_IN_0_X_1_A((byte) 0x95),
|
||||||
|
BAD_STATE_IN_CLEAR_BOLUS_IST_2_AND_VARS((byte) 0x96),
|
||||||
|
BAD_STATE_IN_MAYBE_INC_33_D((byte) 0x97),
|
||||||
|
VALUES_DO_NOT_MATCH_OR_ARE_GREATER_THAN_0_X_97((byte) 0x98);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
FaultEventCode(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FaultEventCode fromByte(byte value) {
|
||||||
|
for (FaultEventCode type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown FaultEventCode: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class FirmwareVersion {
|
||||||
|
private final int major;
|
||||||
|
private final int minor;
|
||||||
|
private final int patch;
|
||||||
|
|
||||||
|
public FirmwareVersion(int major, int minor, int patch) {
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(Locale.getDefault(), "%d.%d.%d", major, minor, patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMajor() {
|
||||||
|
return major;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinor() {
|
||||||
|
return minor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPatch() {
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum LogEventErrorCode {
|
||||||
|
NONE((byte) 0x00),
|
||||||
|
IMMEDIATE_BOLUS_IN_PROGRESS((byte) 0x01),
|
||||||
|
INTERNAL_2_BIT_VARIABLE_SET_AND_MANIPULATED_IN_MAIN_LOOP_ROUTINES_2((byte) 0x02),
|
||||||
|
INTERNAL_2_BIT_VARIABLE_SET_AND_MANIPULATED_IN_MAIN_LOOP_ROUTINES_3((byte) 0x03),
|
||||||
|
INSULIN_STATE_TABLE_CORRUPTION((byte) 0x04);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
LogEventErrorCode(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogEventErrorCode fromByte(byte value) {
|
||||||
|
for (LogEventErrorCode type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown LogEventErrorCode: " + value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse;
|
||||||
|
|
||||||
|
public enum MessageBlockType {
|
||||||
|
VERSION_RESPONSE(0x01),
|
||||||
|
POD_INFO_RESPONSE(0x02),
|
||||||
|
SETUP_POD(0x03),
|
||||||
|
ERROR_RESPONSE(0x06),
|
||||||
|
ASSIGN_ADDRESS(0x07),
|
||||||
|
FAULT_CONFIG(0x08),
|
||||||
|
GET_STATUS(0x0e),
|
||||||
|
ACKNOWLEDGE_ALERT(0x11),
|
||||||
|
BASAL_SCHEDULE_EXTRA(0x13),
|
||||||
|
TEMP_BASAL_EXTRA(0x16),
|
||||||
|
BOLUS_EXTRA(0x17),
|
||||||
|
CONFIGURE_ALERTS(0x19),
|
||||||
|
SET_INSULIN_SCHEDULE(0x1a),
|
||||||
|
DEACTIVATE_POD(0x1c),
|
||||||
|
STATUS_RESPONSE(0x1d),
|
||||||
|
BEEP_CONFIG(0x1e),
|
||||||
|
CANCEL_DELIVERY(0x1f);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
MessageBlockType(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MessageBlockType fromByte(byte value) {
|
||||||
|
for (MessageBlockType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown MessageBlockType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageBlock decode(byte[] encodedData) {
|
||||||
|
switch (this) {
|
||||||
|
case VERSION_RESPONSE:
|
||||||
|
return new VersionResponse(encodedData);
|
||||||
|
case ERROR_RESPONSE:
|
||||||
|
return new ErrorResponse(encodedData);
|
||||||
|
case POD_INFO_RESPONSE:
|
||||||
|
return new PodInfoResponse(encodedData);
|
||||||
|
case STATUS_RESPONSE:
|
||||||
|
return new StatusResponse(encodedData);
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException(this.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public class NonceState {
|
||||||
|
private final long[] table = new long[21];
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public NonceState(int lot, int tid) {
|
||||||
|
initializeTable(lot, tid, (byte) 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonceState(int lot, int tid, byte seed) {
|
||||||
|
initializeTable(lot, tid, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeTable(int lot, int tid, byte seed) {
|
||||||
|
table[0] = (long) (lot & 0xFFFF) + 0x55543DC3L + (((long) (lot) & 0xFFFFFFFFL) >> 16);
|
||||||
|
table[0] = table[0] & 0xFFFFFFFFL;
|
||||||
|
table[1] = (tid & 0xFFFF) + 0xAAAAE44EL + (((long) (tid) & 0xFFFFFFFFL) >> 16);
|
||||||
|
table[1] = table[1] & 0xFFFFFFFFL;
|
||||||
|
index = 0;
|
||||||
|
table[0] += seed;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
table[2 + i] = generateEntry();
|
||||||
|
}
|
||||||
|
index = (int) ((table[0] + table[1]) & 0X0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int generateEntry() {
|
||||||
|
table[0] = (((table[0] >> 16) + (table[0] & 0xFFFF) * 0x5D7FL) & 0xFFFFFFFFL);
|
||||||
|
table[1] = (((table[1] >> 16) + (table[1] & 0xFFFF) * 0x8CA0L) & 0xFFFFFFFFL);
|
||||||
|
return (int) ((table[1] + (table[0] << 16)) & 0xFFFFFFFFL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentNonce() {
|
||||||
|
return (int) table[(2 + index)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void advanceToNextNonce() {
|
||||||
|
int nonce = getCurrentNonce();
|
||||||
|
table[(2 + index)] = generateEntry();
|
||||||
|
index = (nonce & 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NonceState{" +
|
||||||
|
"currentNonce=" + getCurrentNonce() +
|
||||||
|
", index=" + index +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum PacketType {
|
||||||
|
INVALID((byte) 0),
|
||||||
|
POD((byte) 0b111),
|
||||||
|
PDM((byte) 0b101),
|
||||||
|
CON((byte) 0b100),
|
||||||
|
ACK((byte) 0b010);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
PacketType(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PacketType fromByte(byte value) {
|
||||||
|
for (PacketType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown PacketType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxBodyLength() {
|
||||||
|
switch (this) {
|
||||||
|
case ACK:
|
||||||
|
return 4;
|
||||||
|
case CON:
|
||||||
|
case PDM:
|
||||||
|
case POD:
|
||||||
|
return 31;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfo;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoActiveAlerts;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoDataLog;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultAndInitializationTime;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoLowFlashLogDump;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoOlderHighFlashLogDump;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentHighFlashLogDump;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoTestValues;
|
||||||
|
|
||||||
|
public enum PodInfoType {
|
||||||
|
NORMAL((byte) 0x00),
|
||||||
|
ACTIVE_ALERTS((byte) 0x01),
|
||||||
|
FAULT_EVENT((byte) 0x02),
|
||||||
|
DATA_LOG((byte) 0x03), // Similar to types $50 & $51. Returns up to the last 60 dwords of data.
|
||||||
|
FAULT_AND_INITIALIZATION_TIME((byte) 0x05),
|
||||||
|
HARDCODED_TEST_VALUES((byte) 0x06),
|
||||||
|
LOW_FLASH_DUMP_LOG((byte) 0x46), // Starting at $4000
|
||||||
|
RECENT_HIGH_FLASH_LOG_DUMP((byte) 0x50), // Starting at $4200
|
||||||
|
OLDER_HIGH_FLASH_LOG_DUMP((byte) 0x51); // Starting at $4200 but dumps entries before the last 50
|
||||||
|
|
||||||
|
private final byte value;
|
||||||
|
|
||||||
|
PodInfoType(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PodInfoType fromByte(byte value) {
|
||||||
|
for (PodInfoType type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown PodInfoType: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodInfo decode(byte[] encodedData, int bodyLength) {
|
||||||
|
switch (this) {
|
||||||
|
case NORMAL:
|
||||||
|
// We've never observed a PodInfoResponse with 0x00 subtype
|
||||||
|
// Instead, the pod returns a StatusResponse
|
||||||
|
throw new UnsupportedOperationException("Cannot decode PodInfoType.NORMAL");
|
||||||
|
case ACTIVE_ALERTS:
|
||||||
|
return new PodInfoActiveAlerts(encodedData);
|
||||||
|
case FAULT_EVENT:
|
||||||
|
return new PodInfoFaultEvent(encodedData);
|
||||||
|
case DATA_LOG:
|
||||||
|
return new PodInfoDataLog(encodedData, bodyLength);
|
||||||
|
case FAULT_AND_INITIALIZATION_TIME:
|
||||||
|
return new PodInfoFaultAndInitializationTime(encodedData);
|
||||||
|
case HARDCODED_TEST_VALUES:
|
||||||
|
return new PodInfoTestValues(encodedData);
|
||||||
|
case LOW_FLASH_DUMP_LOG:
|
||||||
|
return new PodInfoLowFlashLogDump(encodedData);
|
||||||
|
case RECENT_HIGH_FLASH_LOG_DUMP:
|
||||||
|
return new PodInfoRecentHighFlashLogDump(encodedData, bodyLength);
|
||||||
|
case OLDER_HIGH_FLASH_LOG_DUMP:
|
||||||
|
return new PodInfoOlderHighFlashLogDump(encodedData);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Cannot decode " + this.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum PodProgressStatus {
|
||||||
|
INITIAL_VALUE((byte) 0x00),
|
||||||
|
TANK_POWER_ACTIVATED((byte) 0x01),
|
||||||
|
TANK_FILL_COMPLETED((byte) 0x02),
|
||||||
|
PAIRING_SUCCESS((byte) 0x03),
|
||||||
|
PRIMING((byte) 0x04),
|
||||||
|
READY_FOR_BASAL_SCHEDULE((byte) 0x05),
|
||||||
|
READY_FOR_CANNULA_INSERTION((byte) 0x06),
|
||||||
|
CANNULA_INSERTING((byte) 0x07),
|
||||||
|
RUNNING_ABOVE_FIFTY_UNITS((byte) 0x08),
|
||||||
|
RUNNING_BELOW_FIFTY_UNITS((byte) 0x09),
|
||||||
|
ONE_NOT_USED_BUT_IN_33((byte) 0x0a),
|
||||||
|
TWO_NOT_USED_BUT_IN_33((byte) 0x0b),
|
||||||
|
THREE_NOT_USED_BUT_IN_33((byte) 0x0c),
|
||||||
|
ERROR_EVENT_LOGGED_SHUTTING_DOWN((byte) 0x0d),
|
||||||
|
DELAYED_PRIME((byte) 0x0e),
|
||||||
|
INACTIVE((byte) 0x0f);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
PodProgressStatus(byte value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PodProgressStatus fromByte(byte value) {
|
||||||
|
for (PodProgressStatus type : values()) {
|
||||||
|
if (type.value == value) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown PodProgressStatus: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReadyForDelivery() {
|
||||||
|
return this == RUNNING_ABOVE_FIFTY_UNITS || this == RUNNING_BELOW_FIFTY_UNITS;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public enum SetupProgress {
|
||||||
|
ADDRESS_ASSIGNED,
|
||||||
|
POD_CONFIGURED,
|
||||||
|
STARTING_PRIME,
|
||||||
|
PRIMING,
|
||||||
|
PRIMING_FINISHED,
|
||||||
|
INITIAL_BASAL_SCHEDULE_SET,
|
||||||
|
STARTING_INSERT_CANNULA,
|
||||||
|
CANNULA_INSERTING,
|
||||||
|
COMPLETED;
|
||||||
|
|
||||||
|
public boolean isBefore(SetupProgress other) {
|
||||||
|
return this.ordinal() < other.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAfter(SetupProgress other) {
|
||||||
|
return this.ordinal() > other.ordinal();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
public class TimerAlertTrigger extends AlertTrigger<Duration> {
|
||||||
|
public TimerAlertTrigger(Duration value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
|
||||||
|
|
||||||
|
public class UnitsRemainingAlertTrigger extends AlertTrigger<Double> {
|
||||||
|
public UnitsRemainingAlertTrigger(Double value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
|
||||||
|
public class BasalDeliverySchedule extends DeliverySchedule implements IRawRepresentable {
|
||||||
|
|
||||||
|
private final byte currentSegment;
|
||||||
|
private final int secondsRemaining;
|
||||||
|
private final int pulsesRemaining;
|
||||||
|
private final BasalDeliveryTable basalTable;
|
||||||
|
|
||||||
|
public BasalDeliverySchedule(byte currentSegment, int secondsRemaining, int pulsesRemaining,
|
||||||
|
BasalDeliveryTable basalTable) {
|
||||||
|
this.currentSegment = currentSegment;
|
||||||
|
this.secondsRemaining = secondsRemaining;
|
||||||
|
this.pulsesRemaining = pulsesRemaining;
|
||||||
|
this.basalTable = basalTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRawData() {
|
||||||
|
byte[] rawData = new byte[0];
|
||||||
|
rawData = ByteUtil.concat(rawData, currentSegment);
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(secondsRemaining << 3));
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulsesRemaining));
|
||||||
|
for (BasalTableEntry entry : basalTable.getEntries()) {
|
||||||
|
rawData = ByteUtil.concat(rawData, entry.getRawData());
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsulinScheduleType getType() {
|
||||||
|
return InsulinScheduleType.BASAL_SCHEDULE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChecksum() {
|
||||||
|
int checksum = 0;
|
||||||
|
byte[] rawData = getRawData();
|
||||||
|
for (int i = 0; i < rawData.length && i < 5; i++) {
|
||||||
|
checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]);
|
||||||
|
}
|
||||||
|
for (BasalTableEntry entry : basalTable.getEntries()) {
|
||||||
|
checksum += entry.getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class BasalDeliveryTable {
|
||||||
|
|
||||||
|
public static final int SEGMENT_DURATION = 30 * 60;
|
||||||
|
public static final int MAX_PULSES_PER_RATE_ENTRY = 6400;
|
||||||
|
|
||||||
|
private static final int NUM_SEGMENTS = 48;
|
||||||
|
private static final int MAX_SEGMENTS_PER_ENTRY = 16;
|
||||||
|
|
||||||
|
private List<BasalTableEntry> entries = new ArrayList<>();
|
||||||
|
|
||||||
|
public BasalDeliveryTable(BasalSchedule schedule) {
|
||||||
|
TempSegment[] expandedSegments = new TempSegment[48];
|
||||||
|
|
||||||
|
boolean halfPulseRemainder = false;
|
||||||
|
for (int i = 0; i < NUM_SEGMENTS; i++) {
|
||||||
|
double rate = schedule.rateAt(Duration.standardMinutes(i * 30));
|
||||||
|
int pulsesPerHour = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
int pulsesPerSegment = pulsesPerHour >>> 1;
|
||||||
|
boolean halfPulse = (pulsesPerHour & 0b1) != 0;
|
||||||
|
|
||||||
|
expandedSegments[i] = new TempSegment(pulsesPerSegment + (halfPulseRemainder && halfPulse ? 1 : 0));
|
||||||
|
halfPulseRemainder = halfPulseRemainder != halfPulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TempSegment> segmentsToMerge = new ArrayList<>();
|
||||||
|
|
||||||
|
boolean altSegmentPulse = false;
|
||||||
|
for (TempSegment segment : expandedSegments) {
|
||||||
|
if (segmentsToMerge.isEmpty()) {
|
||||||
|
segmentsToMerge.add(segment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TempSegment firstSegment = segmentsToMerge.get(0);
|
||||||
|
|
||||||
|
int delta = segment.getPulses() - firstSegment.getPulses();
|
||||||
|
if (segmentsToMerge.size() == 1) {
|
||||||
|
altSegmentPulse = delta == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int expectedDelta = altSegmentPulse ? segmentsToMerge.size() % 2 : 0;
|
||||||
|
|
||||||
|
if (expectedDelta != delta || segmentsToMerge.size() == MAX_SEGMENTS_PER_ENTRY) {
|
||||||
|
addBasalTableEntry(segmentsToMerge, altSegmentPulse);
|
||||||
|
segmentsToMerge.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentsToMerge.add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBasalTableEntry(segmentsToMerge, altSegmentPulse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalDeliveryTable(double tempBasalRate, Duration duration) {
|
||||||
|
int pulsesPerHour = (int) Math.round(tempBasalRate / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
int pulsesPerSegment = pulsesPerHour >> 1;
|
||||||
|
boolean alternateSegmentPulse = (pulsesPerHour & 0b1) != 0;
|
||||||
|
|
||||||
|
int remaining = (int) Math.round(duration.getStandardSeconds() / (double) BasalDeliveryTable.SEGMENT_DURATION);
|
||||||
|
|
||||||
|
while (remaining > 0) {
|
||||||
|
int segments = Math.min(MAX_SEGMENTS_PER_ENTRY, remaining);
|
||||||
|
entries.add(new BasalTableEntry(segments, pulsesPerSegment, segments > 1 && alternateSegmentPulse));
|
||||||
|
remaining -= segments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addBasalTableEntry(List<TempSegment> segments, boolean alternateSegmentPulse) {
|
||||||
|
entries.add(new BasalTableEntry(segments.size(), segments.get(0).getPulses(), alternateSegmentPulse));
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalTableEntry[] getEntries() {
|
||||||
|
return entries.toArray(new BasalTableEntry[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte numSegments() {
|
||||||
|
byte numSegments = 0;
|
||||||
|
for (BasalTableEntry entry : entries) {
|
||||||
|
numSegments += entry.getSegments();
|
||||||
|
}
|
||||||
|
return numSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TempSegment {
|
||||||
|
private int pulses;
|
||||||
|
|
||||||
|
public TempSegment(int pulses) {
|
||||||
|
this.pulses = pulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPulses() {
|
||||||
|
return pulses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BasalSchedule {
|
||||||
|
private final List<BasalScheduleEntry> entries;
|
||||||
|
|
||||||
|
public BasalSchedule(List<BasalScheduleEntry> entries) {
|
||||||
|
if (entries == null) {
|
||||||
|
throw new IllegalArgumentException("Entries cannot be null");
|
||||||
|
}
|
||||||
|
this.entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double rateAt(Duration offset) {
|
||||||
|
return lookup(offset).getBasalScheduleEntry().getRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BasalScheduleEntry> getEntries() {
|
||||||
|
return new ArrayList<>(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalScheduleLookupResult lookup(Duration offset) {
|
||||||
|
if (offset.isLongerThan(Duration.standardHours(24)) || offset.isShorterThan(Duration.ZERO)) {
|
||||||
|
throw new IllegalArgumentException("Invalid duration");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BasalScheduleEntry> reversedBasalScheduleEntries = reversedBasalScheduleEntries();
|
||||||
|
|
||||||
|
Duration last = Duration.standardHours(24);
|
||||||
|
int index = 0;
|
||||||
|
for (BasalScheduleEntry entry : reversedBasalScheduleEntries) {
|
||||||
|
if (entry.getStartTime().isShorterThan(offset) || entry.getStartTime().equals(offset)) {
|
||||||
|
return new BasalScheduleLookupResult( //
|
||||||
|
reversedBasalScheduleEntries.size() - (index + 1), //
|
||||||
|
entry, //
|
||||||
|
entry.getStartTime(), //
|
||||||
|
last.minus(entry.getStartTime()));
|
||||||
|
}
|
||||||
|
last = entry.getStartTime();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Basal schedule incomplete");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BasalScheduleEntry> reversedBasalScheduleEntries() {
|
||||||
|
List<BasalScheduleEntry> reversedEntries = new ArrayList<>(entries);
|
||||||
|
Collections.reverse(reversedEntries);
|
||||||
|
return reversedEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BasalScheduleEntry> adjacentEqualRatesMergedEntries() {
|
||||||
|
List<BasalScheduleEntry> mergedEntries = new ArrayList<>();
|
||||||
|
Double lastRate = null;
|
||||||
|
for (BasalScheduleEntry entry : entries) {
|
||||||
|
if (lastRate == null || entry.getRate() != lastRate) {
|
||||||
|
mergedEntries.add(entry);
|
||||||
|
}
|
||||||
|
lastRate = entry.getRate();
|
||||||
|
}
|
||||||
|
return mergedEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BasalScheduleDurationEntry> getDurations() {
|
||||||
|
List<BasalScheduleDurationEntry> durations = new ArrayList<>();
|
||||||
|
Duration last = Duration.standardHours(24);
|
||||||
|
List<BasalScheduleEntry> basalScheduleEntries = reversedBasalScheduleEntries();
|
||||||
|
for (BasalScheduleEntry entry : basalScheduleEntries) {
|
||||||
|
durations.add(new BasalScheduleDurationEntry( //
|
||||||
|
entry.getRate(), //
|
||||||
|
entry.getStartTime(), //
|
||||||
|
last.minus(entry.getStartTime())));
|
||||||
|
last = entry.getStartTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(durations);
|
||||||
|
return durations;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BasalSchedule (" + entries.size() + " entries)";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BasalScheduleDurationEntry {
|
||||||
|
private final double rate;
|
||||||
|
private final Duration duration;
|
||||||
|
private final Duration startTime;
|
||||||
|
|
||||||
|
public BasalScheduleDurationEntry(double rate, Duration startTime, Duration duration) {
|
||||||
|
this.rate = rate;
|
||||||
|
this.duration = duration;
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRate() {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BasalScheduleLookupResult {
|
||||||
|
private final int index;
|
||||||
|
private final BasalScheduleEntry basalScheduleEntry;
|
||||||
|
private final Duration startTime;
|
||||||
|
private final Duration duration;
|
||||||
|
|
||||||
|
public BasalScheduleLookupResult(int index, BasalScheduleEntry basalScheduleEntry, Duration startTime, Duration duration) {
|
||||||
|
this.index = index;
|
||||||
|
this.basalScheduleEntry = basalScheduleEntry;
|
||||||
|
this.startTime = startTime;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalScheduleEntry getBasalScheduleEntry() {
|
||||||
|
return basalScheduleEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.Constants;
|
||||||
|
|
||||||
|
public class BasalScheduleEntry {
|
||||||
|
private final double rate;
|
||||||
|
private final Duration startTime;
|
||||||
|
|
||||||
|
public BasalScheduleEntry(double rate, Duration startTime) {
|
||||||
|
if (rate < 0D) {
|
||||||
|
throw new IllegalArgumentException("Rate should be >= 0");
|
||||||
|
} else if (rate > Constants.MAX_BASAL_RATE) {
|
||||||
|
throw new IllegalArgumentException("Rate exceeds max basal rate");
|
||||||
|
}
|
||||||
|
this.rate = rate;
|
||||||
|
this.startTime = startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRate() {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getStartTime() {
|
||||||
|
return startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BasalScheduleEntry{" +
|
||||||
|
"rate=" + rate +
|
||||||
|
", startTime=" + startTime +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.data.Profile;
|
||||||
|
|
||||||
|
public class BasalScheduleMapper {
|
||||||
|
public static BasalSchedule mapProfileToBasalSchedule(Profile profile) {
|
||||||
|
Profile.ProfileValue[] basalValues = profile.getBasalValues();
|
||||||
|
List<BasalScheduleEntry> entries = new ArrayList<>();
|
||||||
|
for(Profile.ProfileValue basalValue : basalValues) {
|
||||||
|
entries.add(new BasalScheduleEntry(basalValue.value, Duration.standardSeconds(basalValue.timeAsSeconds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BasalSchedule(entries);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
|
||||||
|
public class BasalTableEntry implements IRawRepresentable {
|
||||||
|
|
||||||
|
private final int segments;
|
||||||
|
private final int pulses;
|
||||||
|
private final boolean alternateSegmentPulse;
|
||||||
|
|
||||||
|
public BasalTableEntry(int segments, int pulses, boolean alternateSegmentPulse) {
|
||||||
|
this.segments = segments;
|
||||||
|
this.pulses = pulses;
|
||||||
|
this.alternateSegmentPulse = alternateSegmentPulse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRawData() {
|
||||||
|
byte[] rawData = new byte[2];
|
||||||
|
byte pulsesHighByte = (byte) ((pulses >>> 8) & 0b11);
|
||||||
|
byte pulsesLowByte = (byte) pulses;
|
||||||
|
rawData[0] = (byte) ((byte) ((segments - 1) << 4) + (byte) ((alternateSegmentPulse ? 1 : 0) << 3) + pulsesHighByte);
|
||||||
|
rawData[1] = pulsesLowByte;
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChecksum() {
|
||||||
|
int checksumPerSegment = ByteUtil.convertUnsignedByteToInt((byte) pulses) + (pulses >>> 8);
|
||||||
|
return (checksumPerSegment * segments + (alternateSegmentPulse ? segments / 2 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSegments() {
|
||||||
|
return this.segments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPulses() {
|
||||||
|
return pulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlternateSegmentPulse() {
|
||||||
|
return alternateSegmentPulse;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class BolusDeliverySchedule extends DeliverySchedule implements IRawRepresentable {
|
||||||
|
|
||||||
|
private final double units;
|
||||||
|
private final Duration timeBetweenPulses;
|
||||||
|
|
||||||
|
public BolusDeliverySchedule(double units, Duration timeBetweenPulses) {
|
||||||
|
if (units <= 0D) {
|
||||||
|
throw new IllegalArgumentException("Units should be > 0");
|
||||||
|
} else if (units > OmnipodConst.MAX_BOLUS) {
|
||||||
|
throw new IllegalArgumentException("Units exceeds max bolus");
|
||||||
|
}
|
||||||
|
this.units = units;
|
||||||
|
this.timeBetweenPulses = timeBetweenPulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRawData() {
|
||||||
|
byte[] rawData = new byte[]{1}; // Number of half hour segments
|
||||||
|
|
||||||
|
int pulseCount = (int) Math.round(units / OmnipodConst.POD_PULSE_SIZE);
|
||||||
|
int multiplier = (int) timeBetweenPulses.getStandardSeconds() * 8;
|
||||||
|
int fieldA = pulseCount * multiplier;
|
||||||
|
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(fieldA));
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulseCount));
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulseCount));
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsulinScheduleType getType() {
|
||||||
|
return InsulinScheduleType.BOLUS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChecksum() {
|
||||||
|
int checksum = 0;
|
||||||
|
byte[] rawData = getRawData();
|
||||||
|
for (int i = 0; i < rawData.length && i < 7; i++) {
|
||||||
|
checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]);
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
|
||||||
|
public abstract class DeliverySchedule implements IRawRepresentable {
|
||||||
|
|
||||||
|
public abstract InsulinScheduleType getType();
|
||||||
|
|
||||||
|
public abstract int getChecksum();
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
public enum InsulinScheduleType {
|
||||||
|
BASAL_SCHEDULE(0),
|
||||||
|
TEMP_BASAL_SCHEDULE(1),
|
||||||
|
BOLUS(2);
|
||||||
|
|
||||||
|
private byte value;
|
||||||
|
|
||||||
|
InsulinScheduleType(int value) {
|
||||||
|
this.value = (byte) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InsulinScheduleType fromByte(byte input) {
|
||||||
|
for (InsulinScheduleType type : values()) {
|
||||||
|
if (type.value == input) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
|
||||||
|
public class RateEntry implements IRawRepresentable {
|
||||||
|
|
||||||
|
private final double totalPulses;
|
||||||
|
// We use a double for the delay between pulses because the Joda time API lacks precision for our calculations
|
||||||
|
private final double delayBetweenPulsesInSeconds;
|
||||||
|
|
||||||
|
public RateEntry(double totalPulses, double delayBetweenPulsesInSeconds) {
|
||||||
|
this.totalPulses = totalPulses;
|
||||||
|
this.delayBetweenPulsesInSeconds = delayBetweenPulsesInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<RateEntry> createEntries(double rate, Duration duration) {
|
||||||
|
List<RateEntry> entries = new ArrayList<>();
|
||||||
|
int remainingSegments = (int) Math.round(duration.getStandardSeconds() / 1800.0);
|
||||||
|
double pulsesPerSegment = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE) / 2.0;
|
||||||
|
int maxSegmentsPerEntry = pulsesPerSegment > 0 ? (int) (BasalDeliveryTable.MAX_PULSES_PER_RATE_ENTRY / pulsesPerSegment) : 1;
|
||||||
|
|
||||||
|
double durationInHours = duration.getStandardSeconds() / 3600.0;
|
||||||
|
|
||||||
|
double remainingPulses = rate * durationInHours / OmnipodConst.POD_PULSE_SIZE;
|
||||||
|
double delayBetweenPulses = 3600 / rate * OmnipodConst.POD_PULSE_SIZE;
|
||||||
|
|
||||||
|
while (remainingSegments > 0) {
|
||||||
|
if (rate == 0.0) {
|
||||||
|
entries.add(new RateEntry(0, 30D * 60));
|
||||||
|
remainingSegments -= 1;
|
||||||
|
} else {
|
||||||
|
int numSegments = Math.min(maxSegmentsPerEntry, (int) Math.round(remainingPulses / pulsesPerSegment));
|
||||||
|
double totalPulses = pulsesPerSegment * numSegments;
|
||||||
|
entries.add(new RateEntry(totalPulses, delayBetweenPulses));
|
||||||
|
remainingSegments -= numSegments;
|
||||||
|
remainingPulses -= totalPulses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalPulses() {
|
||||||
|
return totalPulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDelayBetweenPulsesInSeconds() {
|
||||||
|
return delayBetweenPulsesInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRawData() {
|
||||||
|
byte[] rawData = new byte[0];
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16((int) Math.round(totalPulses * 10)));
|
||||||
|
if (totalPulses == 0) {
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt((int) (delayBetweenPulsesInSeconds * 1000 * 1000)));
|
||||||
|
} else {
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt((int) (delayBetweenPulsesInSeconds * 1000 * 100)));
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable;
|
||||||
|
|
||||||
|
public class TempBasalDeliverySchedule extends DeliverySchedule implements IRawRepresentable {
|
||||||
|
|
||||||
|
private final int secondsRemaining;
|
||||||
|
private final int firstSegmentPulses;
|
||||||
|
private final BasalDeliveryTable basalTable;
|
||||||
|
|
||||||
|
public TempBasalDeliverySchedule(int secondsRemaining, int firstSegmentPulses, BasalDeliveryTable basalTable) {
|
||||||
|
this.secondsRemaining = secondsRemaining;
|
||||||
|
this.firstSegmentPulses = firstSegmentPulses;
|
||||||
|
this.basalTable = basalTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRawData() {
|
||||||
|
byte[] rawData = new byte[0];
|
||||||
|
rawData = ByteUtil.concat(rawData, basalTable.numSegments());
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(secondsRemaining << 3));
|
||||||
|
rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(firstSegmentPulses));
|
||||||
|
for (BasalTableEntry entry : basalTable.getEntries()) {
|
||||||
|
rawData = ByteUtil.concat(rawData, entry.getRawData());
|
||||||
|
}
|
||||||
|
return rawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsulinScheduleType getType() {
|
||||||
|
return InsulinScheduleType.TEMP_BASAL_SCHEDULE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChecksum() {
|
||||||
|
int checksum = 0;
|
||||||
|
byte[] rawData = getRawData();
|
||||||
|
for (int i = 0; i < rawData.length && i < 5; i++) {
|
||||||
|
checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]);
|
||||||
|
}
|
||||||
|
for (BasalTableEntry entry : basalTable.getEntries()) {
|
||||||
|
checksum += entry.getChecksum();
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSecondsRemaining() {
|
||||||
|
return secondsRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFirstSegmentPulses() {
|
||||||
|
return firstSegmentPulses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalDeliveryTable getBasalTable() {
|
||||||
|
return basalTable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.state;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.NonceState;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil;
|
||||||
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
|
public class PodSessionState extends PodState {
|
||||||
|
private final Map<AlertSlot, AlertType> configuredAlerts;
|
||||||
|
private final DateTime activatedAt;
|
||||||
|
private final FirmwareVersion piVersion;
|
||||||
|
private final FirmwareVersion pmVersion;
|
||||||
|
private final int lot;
|
||||||
|
private final int tid;
|
||||||
|
private boolean suspended;
|
||||||
|
|
||||||
|
private DateTimeZone timeZone;
|
||||||
|
private NonceState nonceState;
|
||||||
|
private SetupProgress setupProgress;
|
||||||
|
private AlertSet activeAlerts;
|
||||||
|
private BasalSchedule basalSchedule;
|
||||||
|
private DeliveryStatus lastDeliveryStatus;
|
||||||
|
|
||||||
|
public PodSessionState(DateTimeZone timeZone, int address, DateTime activatedAt, FirmwareVersion piVersion,
|
||||||
|
FirmwareVersion pmVersion, int lot, int tid, int packetNumber, int messageNumber) {
|
||||||
|
super(address, messageNumber, packetNumber);
|
||||||
|
if (timeZone == null) {
|
||||||
|
throw new IllegalArgumentException("Time zone can not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
suspended = false;
|
||||||
|
configuredAlerts = new HashMap<>();
|
||||||
|
configuredAlerts.put(AlertSlot.SLOT7, AlertType.FINISH_SETUP_REMINDER);
|
||||||
|
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
this.setupProgress = SetupProgress.ADDRESS_ASSIGNED;
|
||||||
|
this.activatedAt = activatedAt;
|
||||||
|
this.piVersion = piVersion;
|
||||||
|
this.pmVersion = pmVersion;
|
||||||
|
this.lot = lot;
|
||||||
|
this.tid = tid;
|
||||||
|
this.nonceState = new NonceState(lot, tid);
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertType getConfiguredAlertType(AlertSlot alertSlot) {
|
||||||
|
return configuredAlerts.get(alertSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putConfiguredAlert(AlertSlot alertSlot, AlertType alertType) {
|
||||||
|
configuredAlerts.put(alertSlot, alertType);
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeConfiguredAlert(AlertSlot alertSlot) {
|
||||||
|
configuredAlerts.remove(alertSlot);
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getActivatedAt() {
|
||||||
|
return activatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVersion getPiVersion() {
|
||||||
|
return piVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FirmwareVersion getPmVersion() {
|
||||||
|
return pmVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLot() {
|
||||||
|
return lot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTid() {
|
||||||
|
return tid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) {
|
||||||
|
int sum = (sentNonce & 0xFFFF)
|
||||||
|
+ OmniCRC.crc16lookup[sequenceNumber]
|
||||||
|
+ (this.lot & 0xFFFF)
|
||||||
|
+ (this.tid & 0xFFFF);
|
||||||
|
int seed = ((sum & 0xFFFF) ^ syncWord);
|
||||||
|
|
||||||
|
this.nonceState = new NonceState(lot, tid, (byte) (seed & 0xFF));
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentNonce() {
|
||||||
|
return nonceState.getCurrentNonce();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void advanceToNextNonce() {
|
||||||
|
nonceState.advanceToNextNonce();
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SetupProgress getSetupProgress() {
|
||||||
|
return setupProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setSetupProgress(SetupProgress setupProgress) {
|
||||||
|
if (setupProgress == null) {
|
||||||
|
throw new IllegalArgumentException("Setup state cannot be null");
|
||||||
|
}
|
||||||
|
this.setupProgress = setupProgress;
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuspended() {
|
||||||
|
return suspended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveAlerts() {
|
||||||
|
return activeAlerts != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertSet getActiveAlerts() {
|
||||||
|
return activeAlerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTimeZone getTimeZone() {
|
||||||
|
return timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeZone(DateTimeZone timeZone) {
|
||||||
|
if (timeZone == null) {
|
||||||
|
throw new IllegalArgumentException("Time zone can not be null");
|
||||||
|
}
|
||||||
|
this.timeZone = timeZone;
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getTime() {
|
||||||
|
return DateTime.now().withZone(timeZone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Duration getScheduleOffset() {
|
||||||
|
DateTime now = getTime();
|
||||||
|
DateTime startOfDay = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(),
|
||||||
|
0, 0, 0, timeZone);
|
||||||
|
return new Duration(startOfDay, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNonceState() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPacketNumber(int packetNumber) {
|
||||||
|
super.setPacketNumber(packetNumber);
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessageNumber(int messageNumber) {
|
||||||
|
super.setMessageNumber(messageNumber);
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BasalSchedule getBasalSchedule() {
|
||||||
|
return basalSchedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBasalSchedule(BasalSchedule basalSchedule) {
|
||||||
|
this.basalSchedule = basalSchedule;
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeliveryStatus getLastDeliveryStatus() {
|
||||||
|
return lastDeliveryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateFromStatusResponse(StatusResponse statusResponse) {
|
||||||
|
suspended = (statusResponse.getDeliveryStatus() == DeliveryStatus.SUSPENDED);
|
||||||
|
activeAlerts = statusResponse.getAlerts();
|
||||||
|
lastDeliveryStatus = statusResponse.getDeliveryStatus();
|
||||||
|
store();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void store() {
|
||||||
|
Gson gson = OmnipodUtil.getGsonInstance();
|
||||||
|
SP.putString(OmnipodConst.Prefs.PodState, gson.toJson(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodSessionState{" +
|
||||||
|
"activatedAt=" + activatedAt +
|
||||||
|
", piVersion=" + piVersion +
|
||||||
|
", pmVersion=" + pmVersion +
|
||||||
|
", lot=" + lot +
|
||||||
|
", tid=" + tid +
|
||||||
|
", suspended=" + suspended +
|
||||||
|
", timeZone=" + timeZone +
|
||||||
|
", nonceState=" + nonceState +
|
||||||
|
", setupProgress=" + setupProgress +
|
||||||
|
", configuredAlerts=" + configuredAlerts +
|
||||||
|
", activeAlerts=" + activeAlerts +
|
||||||
|
", basalSchedule=" + basalSchedule +
|
||||||
|
", lastDeliveryStatus=" + lastDeliveryStatus +
|
||||||
|
", address=" + address +
|
||||||
|
", packetNumber=" + packetNumber +
|
||||||
|
", messageNumber=" + messageNumber +
|
||||||
|
", faultEvent=" + faultEvent +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.state;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
|
||||||
|
public class PodSetupState extends PodState {
|
||||||
|
public PodSetupState(int address, int packetNumber, int messageNumber) {
|
||||||
|
super(address, packetNumber, messageNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNonceState() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentNonce() {
|
||||||
|
throw new UnsupportedOperationException("PodSetupState does not have a nonce state");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void advanceToNextNonce() {
|
||||||
|
throw new UnsupportedOperationException("PodSetupState does not have a nonce state");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) {
|
||||||
|
throw new UnsupportedOperationException("PodSetupState does not have a nonce state");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateFromStatusResponse(StatusResponse statusResponse) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PodSetupState{" +
|
||||||
|
"address=" + address +
|
||||||
|
", packetNumber=" + packetNumber +
|
||||||
|
", messageNumber=" + messageNumber +
|
||||||
|
", faultEvent=" + faultEvent +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.defs.state;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
|
||||||
|
|
||||||
|
public abstract class PodState {
|
||||||
|
protected final int address;
|
||||||
|
protected int packetNumber;
|
||||||
|
protected int messageNumber;
|
||||||
|
|
||||||
|
protected PodInfoFaultEvent faultEvent;
|
||||||
|
|
||||||
|
public PodState(int address, int packetNumber, int messageNumber) {
|
||||||
|
this.address = address;
|
||||||
|
this.packetNumber = packetNumber;
|
||||||
|
this.messageNumber = messageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean hasNonceState();
|
||||||
|
|
||||||
|
public abstract int getCurrentNonce();
|
||||||
|
|
||||||
|
public abstract void advanceToNextNonce();
|
||||||
|
|
||||||
|
public abstract void resyncNonce(int syncWord, int sentNonce, int sequenceNumber);
|
||||||
|
|
||||||
|
public abstract void updateFromStatusResponse(StatusResponse statusResponse);
|
||||||
|
|
||||||
|
public int getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageNumber() {
|
||||||
|
return messageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageNumber(int messageNumber) {
|
||||||
|
this.messageNumber = messageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPacketNumber() {
|
||||||
|
return packetNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPacketNumber(int packetNumber) {
|
||||||
|
this.packetNumber = packetNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increaseMessageNumber(int increment) {
|
||||||
|
setMessageNumber((messageNumber + increment) & 0b1111);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increasePacketNumber(int increment) {
|
||||||
|
setPacketNumber((packetNumber + increment) & 0b11111);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFaultEvent() {
|
||||||
|
return faultEvent != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodInfoFaultEvent getFaultEvent() {
|
||||||
|
return faultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaultEvent(PodInfoFaultEvent faultEvent) {
|
||||||
|
this.faultEvent = faultEvent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
public class CrcMismatchException extends OmnipodException {
|
||||||
|
public CrcMismatchException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CrcMismatchException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
|
||||||
|
|
||||||
|
public class NonceOutOfSyncException extends PodReturnedErrorResponseException {
|
||||||
|
public NonceOutOfSyncException(ErrorResponse errorResponse) {
|
||||||
|
super(errorResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonceOutOfSyncException(ErrorResponse errorResponse, Throwable cause) {
|
||||||
|
super(errorResponse, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
public class NotEnoughDataException extends OmnipodException {
|
||||||
|
public NotEnoughDataException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NotEnoughDataException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
public class OmnipodException extends RuntimeException {
|
||||||
|
public OmnipodException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OmnipodException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent;
|
||||||
|
|
||||||
|
public class PodFaultException extends OmnipodException {
|
||||||
|
private final PodInfoFaultEvent faultEvent;
|
||||||
|
|
||||||
|
public PodFaultException(PodInfoFaultEvent faultEvent) {
|
||||||
|
super(describePodFault(faultEvent));
|
||||||
|
this.faultEvent = faultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodFaultException(PodInfoFaultEvent faultEvent, Throwable cause) {
|
||||||
|
super(describePodFault(faultEvent), cause);
|
||||||
|
this.faultEvent = faultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String describePodFault(PodInfoFaultEvent faultEvent) {
|
||||||
|
return String.format(Locale.getDefault(), "Pod fault (%d): %s", faultEvent.getFaultEventCode().getValue(),
|
||||||
|
faultEvent.getFaultEventCode().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodInfoFaultEvent getFaultEvent() {
|
||||||
|
return faultEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace() {
|
||||||
|
System.out.println(faultEvent.toString());
|
||||||
|
super.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.exception;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse;
|
||||||
|
|
||||||
|
public class PodReturnedErrorResponseException extends OmnipodException {
|
||||||
|
private final ErrorResponse errorResponse;
|
||||||
|
|
||||||
|
public PodReturnedErrorResponseException(ErrorResponse errorResponse) {
|
||||||
|
super("Pod returned error response");
|
||||||
|
this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PodReturnedErrorResponseException(ErrorResponse errorResponse, Throwable cause) {
|
||||||
|
super("Pod returned error response", cause);
|
||||||
|
this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrorResponse getErrorResponse() {
|
||||||
|
return errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace() {
|
||||||
|
System.out.println(errorResponse.toString());
|
||||||
|
super.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.util;
|
||||||
|
|
||||||
|
public class OmniCRC {
|
||||||
|
public static final int[] crc16lookup = new int[] {
|
||||||
|
0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011,
|
||||||
|
0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022,
|
||||||
|
0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072,
|
||||||
|
0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041,
|
||||||
|
0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2,
|
||||||
|
0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1,
|
||||||
|
0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1,
|
||||||
|
0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082,
|
||||||
|
0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192,
|
||||||
|
0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1,
|
||||||
|
0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1,
|
||||||
|
0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2,
|
||||||
|
0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151,
|
||||||
|
0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162,
|
||||||
|
0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132,
|
||||||
|
0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101,
|
||||||
|
0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312,
|
||||||
|
0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321,
|
||||||
|
0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371,
|
||||||
|
0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342,
|
||||||
|
0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1,
|
||||||
|
0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2,
|
||||||
|
0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2,
|
||||||
|
0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381,
|
||||||
|
0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291,
|
||||||
|
0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2,
|
||||||
|
0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2,
|
||||||
|
0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1,
|
||||||
|
0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252,
|
||||||
|
0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261,
|
||||||
|
0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231,
|
||||||
|
0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202
|
||||||
|
};
|
||||||
|
public static final int[] crc8lookup = new int[]{
|
||||||
|
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
||||||
|
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
||||||
|
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
||||||
|
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
||||||
|
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
||||||
|
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
||||||
|
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
||||||
|
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
||||||
|
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
||||||
|
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
||||||
|
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
||||||
|
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
||||||
|
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
||||||
|
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
||||||
|
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
||||||
|
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public static int crc16(byte[] bytes) {
|
||||||
|
int crc = 0x0000;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
crc = (crc >> 8) ^ crc16lookup[(crc ^ b) & 0xff];
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte crc8(byte[] bytes) {
|
||||||
|
byte crc = 0x00;
|
||||||
|
for (byte b : bytes) {
|
||||||
|
crc = (byte) crc8lookup[(crc ^ b) & 0xff];
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.util;
|
package info.nightscout.androidaps.plugins.pump.omnipod.util;
|
||||||
|
|
||||||
|
import org.joda.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by andy on 4.8.2019
|
* Created by andy on 4.8.2019
|
||||||
*/
|
*/
|
||||||
|
@ -9,12 +11,10 @@ public class OmnipodConst {
|
||||||
static final String Prefix = "AAPS.Omnipod.";
|
static final String Prefix = "AAPS.Omnipod.";
|
||||||
|
|
||||||
public class Prefs {
|
public class Prefs {
|
||||||
|
public static final String PodState = Prefix + "pod_state";
|
||||||
//public static final int BatteryType = R.string.pref_key_medtronic_battery_type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Statistics {
|
public class Statistics {
|
||||||
|
|
||||||
public static final String StatsPrefix = "omnipod_";
|
public static final String StatsPrefix = "omnipod_";
|
||||||
public static final String FirstPumpStart = Prefix + "first_pump_use";
|
public static final String FirstPumpStart = Prefix + "first_pump_use";
|
||||||
public static final String LastGoodPumpCommunicationTime = Prefix + "lastGoodPumpCommunicationTime";
|
public static final String LastGoodPumpCommunicationTime = Prefix + "lastGoodPumpCommunicationTime";
|
||||||
|
@ -25,4 +25,20 @@ public class OmnipodConst {
|
||||||
public static final String LastPumpHistoryEntry = StatsPrefix + "pump_history_entry";
|
public static final String LastPumpHistoryEntry = StatsPrefix + "pump_history_entry";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final double POD_PULSE_SIZE = 0.05;
|
||||||
|
public static final double MAX_RESERVOIR_READING = 50.0;
|
||||||
|
public static final double MAX_BOLUS = 30.0;
|
||||||
|
public static final double MAX_BASAL_RATE = 30.0;
|
||||||
|
public static final Duration MAX_TEMP_BASAL_DURATION = Duration.standardHours(12);
|
||||||
|
public static final int DEFAULT_ADDRESS = 0xffffffff;
|
||||||
|
|
||||||
|
public static final Duration SERVICE_DURATION = Duration.standardHours(80);
|
||||||
|
public static final Duration EXPIRATION_ALERT_WINDOW = Duration.standardHours(2);
|
||||||
|
public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(2);
|
||||||
|
public static final Duration END_OF_SERVICE_IMMINENT_WINDOW = Duration.standardHours(1);
|
||||||
|
|
||||||
|
public static final double POD_PRIME_BOLUS_UNITS = 2.6;
|
||||||
|
public static final double POD_CANNULA_INSERTION_BOLUS_UNITS = 0.5;
|
||||||
|
public static final Duration POD_PRIME_DURATION = Duration.standardSeconds(55);
|
||||||
|
public static final Duration POD_CANNULA_INSERTION_DURATION = Duration.standardSeconds(10);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,13 @@ import android.content.Context;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
import org.joda.time.format.ISODateTimeFormat;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -37,20 +43,12 @@ public class OmnipodUtil extends RileyLinkUtil {
|
||||||
private static RileyLinkOmnipodService omnipodService;
|
private static RileyLinkOmnipodService omnipodService;
|
||||||
private static OmnipodPumpStatus omnipodPumpStatus;
|
private static OmnipodPumpStatus omnipodPumpStatus;
|
||||||
private static OmnipodCommandType currentCommand;
|
private static OmnipodCommandType currentCommand;
|
||||||
public static Gson gsonInstance = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
private static Gson gsonInstance = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
|
||||||
public static Gson gsonInstancePretty = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
|
|
||||||
.setPrettyPrinting().create();
|
|
||||||
|
|
||||||
|
|
||||||
public static Gson getGsonInstance() {
|
public static Gson getGsonInstance() {
|
||||||
return gsonInstance;
|
return gsonInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Gson getGsonInstancePretty() {
|
|
||||||
return gsonInstancePretty;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static int makeUnsignedShort(int b2, int b1) {
|
public static int makeUnsignedShort(int b2, int b1) {
|
||||||
int k = (b2 & 0xff) << 8 | b1 & 0xff;
|
int k = (b2 & 0xff) << 8 | b1 & 0xff;
|
||||||
return k;
|
return k;
|
||||||
|
@ -167,4 +165,18 @@ public class OmnipodUtil extends RileyLinkUtil {
|
||||||
public static void setPumpStatus(OmnipodPumpStatus omnipodPumpStatus) {
|
public static void setPumpStatus(OmnipodPumpStatus omnipodPumpStatus) {
|
||||||
OmnipodUtil.omnipodPumpStatus = omnipodPumpStatus;
|
OmnipodUtil.omnipodPumpStatus = omnipodPumpStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Gson createGson() {
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(DateTime.class, (JsonSerializer<DateTime>) (dateTime, typeOfSrc, context) ->
|
||||||
|
new JsonPrimitive(ISODateTimeFormat.dateTime().print(dateTime)))
|
||||||
|
.registerTypeAdapter(DateTime.class, (JsonDeserializer<DateTime>) (json, typeOfT, context) ->
|
||||||
|
ISODateTimeFormat.dateTime().parseDateTime(json.getAsString()))
|
||||||
|
.registerTypeAdapter(DateTimeZone.class, (JsonSerializer<DateTimeZone>) (timeZone, typeOfSrc, context) ->
|
||||||
|
new JsonPrimitive(timeZone.getID()))
|
||||||
|
.registerTypeAdapter(DateTimeZone.class, (JsonDeserializer<DateTimeZone>) (json, typeOfT, context) ->
|
||||||
|
DateTimeZone.forID(json.getAsString()));
|
||||||
|
|
||||||
|
return gsonBuilder.create();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.Seconds;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PairService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PairActionTest {
|
||||||
|
@Mock
|
||||||
|
private PairService pairService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private OmnipodCommunicationService communicationService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private VersionResponse assignAddressResponse;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private VersionResponse confirmPairingResponse;
|
||||||
|
|
||||||
|
// FIXME test fails because PodState requires android context to be able to store itself
|
||||||
|
// Proposal: storing PodState should happen elsewhere, not in the class itself
|
||||||
|
@Ignore
|
||||||
|
@Test
|
||||||
|
public void testServiceInvocationFromCapture() {
|
||||||
|
// Setup
|
||||||
|
int address = 0x1f173217;
|
||||||
|
|
||||||
|
FirmwareVersion pmVersion = new FirmwareVersion(1, 2, 3);
|
||||||
|
FirmwareVersion piVersion = new FirmwareVersion(4, 5, 6);
|
||||||
|
|
||||||
|
when(assignAddressResponse.getLot()).thenReturn(13);
|
||||||
|
when(assignAddressResponse.getTid()).thenReturn(8);
|
||||||
|
|
||||||
|
when(confirmPairingResponse.getLot()).thenReturn(13);
|
||||||
|
when(confirmPairingResponse.getTid()).thenReturn(8);
|
||||||
|
when(confirmPairingResponse.getPmVersion()).thenReturn(pmVersion);
|
||||||
|
when(confirmPairingResponse.getPiVersion()).thenReturn(piVersion);
|
||||||
|
|
||||||
|
when(pairService.executeAssignAddressCommand(eq(communicationService), argThat(setupState -> setupState.getAddress() == address))) //
|
||||||
|
.thenReturn(assignAddressResponse);
|
||||||
|
|
||||||
|
when(pairService.executeConfigurePodCommand(eq(communicationService), argThat(setupState -> setupState.getAddress() == address), eq(13), eq(8), any(DateTime.class))) //
|
||||||
|
.thenReturn(confirmPairingResponse);
|
||||||
|
|
||||||
|
// SUT
|
||||||
|
PodSessionState podState = new PairAction(pairService, address).execute(communicationService);
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
verify(pairService).executeAssignAddressCommand(any(), any());
|
||||||
|
verify(pairService).executeConfigurePodCommand(any(), any(), anyInt(), anyInt(), any(DateTime.class));
|
||||||
|
|
||||||
|
verifyNoMoreInteractions(pairService);
|
||||||
|
|
||||||
|
// The InitializePodAction should not directly invoke the OmnipodCommunicationService
|
||||||
|
// This should be done by the InitializePodService
|
||||||
|
verifyZeroInteractions(communicationService);
|
||||||
|
|
||||||
|
Seconds seconds = Seconds.secondsBetween(podState.getActivatedAt(), DateTime.now());
|
||||||
|
|
||||||
|
assertTrue("Expected the pod activation time to be less then 3 seconds ago", seconds.isLessThan(Seconds.seconds(3)));
|
||||||
|
assertEquals(13, podState.getLot());
|
||||||
|
assertEquals(8, podState.getTid());
|
||||||
|
assertEquals(piVersion, podState.getPiVersion());
|
||||||
|
assertEquals(pmVersion, podState.getPmVersion());
|
||||||
|
//assertEquals(0xTODO, podState.getCurrentNonce());
|
||||||
|
//assertEquals(0xTODO, podState.getPacketNumber());
|
||||||
|
//assertEquals(0xTODO, podState.getMessageNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add scenarios (?)
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.Constants;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||||
|
import static org.powermock.api.mockito.PowerMockito.when;
|
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
|
public class PairServiceTest {
|
||||||
|
@Mock
|
||||||
|
private OmnipodCommunicationService communicationService;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
verifyNoMoreInteractions(communicationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteAssignAddressCommand() {
|
||||||
|
// Setup
|
||||||
|
PodSetupState setupState = new PodSetupState(0x1f173217, 0x00, 0x00);
|
||||||
|
VersionResponse response = mock(VersionResponse.class);
|
||||||
|
ArgumentCaptor<OmnipodMessage> messageCaptor = ArgumentCaptor.forClass(OmnipodMessage.class);
|
||||||
|
|
||||||
|
when(communicationService.exchangeMessages(any(), any(), any(), any(), any())).thenReturn(response);
|
||||||
|
|
||||||
|
// SUT
|
||||||
|
VersionResponse versionResponse = new PairService().executeAssignAddressCommand(communicationService, setupState);
|
||||||
|
|
||||||
|
// verify
|
||||||
|
verify(communicationService).exchangeMessages(eq(VersionResponse.class), eq(setupState), messageCaptor.capture(), eq(Constants.DEFAULT_ADDRESS), eq(0x1f173217));
|
||||||
|
verifyNoMoreInteractions(communicationService);
|
||||||
|
verifyZeroInteractions(response);
|
||||||
|
|
||||||
|
assertEquals(versionResponse, response);
|
||||||
|
|
||||||
|
OmnipodMessage message = messageCaptor.getValue();
|
||||||
|
byte[] expectedMessage = ByteUtil.fromHexString("ffffffff000607041f17321700fa"); // from https://github.com/openaps/openomni/wiki/Priming-and-Deploying-New-Pod-%28jweismann%29
|
||||||
|
assertArrayEquals(expectedMessage, message.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExecuteConfigurePodCommand() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO add scenarios
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue