Add files from Omnipod test app

This commit is contained in:
Bart Sopers 2019-08-11 22:59:18 +02:00
parent ca325c18e9
commit aa15c80924
126 changed files with 6683 additions and 18 deletions

View file

@ -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() {

View file

@ -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;
}
} }

View file

@ -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) */

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.message;
public interface IRawRepresentable {
byte[] getRawData();
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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;
}
}

View file

@ -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() +
'}';
}
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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) +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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() +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.plugins.pump.omnipod.defs;
public class UnitsRemainingAlertTrigger extends AlertTrigger<Double> {
public UnitsRemainingAlertTrigger(Double value) {
super(value);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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 +
'}';
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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);
} }

View file

@ -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();
}
} }

View file

@ -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 (?)
}

View file

@ -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