Accept ACK responses for ConfigurePodCommand

This commit is contained in:
Bart Sopers 2019-12-23 17:40:10 +01:00
parent 91648a935f
commit 91f6539328
8 changed files with 119 additions and 271 deletions

View file

@ -13,18 +13,18 @@ import java.util.concurrent.TimeUnit;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AcknowledgeAlertsAction;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AssignAddressAction;
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.ConfigurePodAction;
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.command.CancelDeliveryCommand;
@ -88,16 +88,20 @@ public class OmnipodManager {
try {
if (podState == null) {
podState = communicationService.executeAction(
new PairAction(new PairService(), podStateChangedHandler));
}
if (!podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) {
new AssignAddressAction(podStateChangedHandler));
} else if (SetupProgress.PRIMING.isBefore(podState.getSetupProgress())) {
throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress());
}
if (SetupProgress.ADDRESS_ASSIGNED.equals(podState.getSetupProgress())) {
communicationService.executeAction(new ConfigurePodAction(podState));
}
communicationService.executeAction(new PrimeAction(new PrimeService(), podState));
} finally {
logCommandExecutionFinished("pairAndPrime");
}
long delayInSeconds = calculateBolusDuration(OmnipodConst.POD_PRIME_BOLUS_UNITS, OmnipodConst.POD_PRIMING_DELIVERY_RATE).getStandardSeconds();
return Single.timer(delayInSeconds, TimeUnit.SECONDS) //

View file

@ -0,0 +1,50 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
import org.joda.time.DateTimeZone;
import java.util.Collections;
import java.util.Random;
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.response.VersionResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class AssignAddressAction implements OmnipodAction<PodSessionState> {
private final int address;
private final PodStateChangedHandler podStateChangedHandler;
public AssignAddressAction(PodStateChangedHandler podStateChangedHandler) {
this.address = generateRandomAddress();
this.podStateChangedHandler = podStateChangedHandler;
}
private static int generateRandomAddress() {
return 0x1f000000 | (new Random().nextInt() & 0x000fffff);
}
@Override
public PodSessionState execute(OmnipodCommunicationService communicationService) {
PodSetupState setupState = new PodSetupState(address, 0x00, 0x00);
AssignAddressCommand assignAddress = new AssignAddressCommand(setupState.getAddress());
OmnipodMessage assignAddressMessage = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
Collections.singletonList(assignAddress), setupState.getMessageNumber());
VersionResponse assignAddressResponse = communicationService.exchangeMessages(VersionResponse.class, setupState, assignAddressMessage,
OmnipodConst.DEFAULT_ADDRESS, setupState.getAddress());
DateTimeZone timeZone = DateTimeZone.getDefault();
PodSessionState podState = new PodSessionState(timeZone, address, assignAddressResponse.getPiVersion(),
assignAddressResponse.getPmVersion(), assignAddressResponse.getLot(), assignAddressResponse.getTid(),
setupState.getPacketNumber(), 0x00); // At this point, for an unknown reason, the pod starts counting messages from 0 again
podState.setStateChangedHandler(podStateChangedHandler);
return podState;
}
}

View file

@ -0,0 +1,59 @@
package info.nightscout.androidaps.plugins.pump.omnipod.comm.action;
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.ConfigurePodCommand;
import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType;
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;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPacketTypeException;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPodProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
public class ConfigurePodAction implements OmnipodAction<VersionResponse> {
private final PodSessionState podState;
public ConfigurePodAction(PodSessionState podState) {
this.podState = podState;
}
@Override
public VersionResponse execute(OmnipodCommunicationService communicationService) {
if (!podState.getSetupProgress().equals(SetupProgress.ADDRESS_ASSIGNED)) {
throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress());
}
DateTime activationDate = DateTime.now(podState.getTimeZone());
ConfigurePodCommand configurePodCommand = new ConfigurePodCommand(podState.getAddress(), activationDate,
podState.getLot(), podState.getTid());
OmnipodMessage message = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS,
Collections.singletonList(configurePodCommand), podState.getMessageNumber());
VersionResponse configurePodResponse;
try {
configurePodResponse = communicationService.exchangeMessages(VersionResponse.class, podState,
message, OmnipodConst.DEFAULT_ADDRESS, podState.getAddress());
} catch (IllegalPacketTypeException ex) {
if (PacketType.ACK.equals(ex.getActual())) {
// Pod is already configured
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
return null;
}
throw ex;
}
if (configurePodResponse.getPodProgressStatus() != PodProgressStatus.PAIRING_SUCCESS) {
throw new IllegalPodProgressException(PodProgressStatus.PAIRING_SUCCESS, configurePodResponse.getPodProgressStatus());
}
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
return configurePodResponse;
}
}

View file

@ -1,59 +0,0 @@
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;
import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException;
public class PairAction implements OmnipodAction<PodSessionState> {
private final PairService service;
private final int address;
private final PodStateChangedHandler podStateChangedHandler;
public PairAction(PairService pairService, int address, PodStateChangedHandler podStateChangedHandler) {
if (pairService == null) {
throw new ActionInitializationException("Pair service cannot be null");
}
this.service = pairService;
this.address = address;
this.podStateChangedHandler = podStateChangedHandler;
}
public PairAction(PairService service, PodStateChangedHandler podStateChangedHandler) {
this(service, generateRandomAddress(), podStateChangedHandler);
}
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.setStateChangedHandler(podStateChangedHandler);
podState.setSetupProgress(SetupProgress.POD_CONFIGURED);
return podState;
}
}

View file

@ -1,45 +0,0 @@
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.IllegalPodProgressException;
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 IllegalPodProgressException(PodProgressStatus.PAIRING_SUCCESS, configurePodResponse.getPodProgressStatus());
}
return configurePodResponse;
}
}

View file

@ -44,7 +44,7 @@ public class PodSessionState extends PodState {
private BasalSchedule basalSchedule;
private DeliveryStatus lastDeliveryStatus;
public PodSessionState(DateTimeZone timeZone, int address, DateTime activatedAt, FirmwareVersion piVersion,
public PodSessionState(DateTimeZone timeZone, int address, FirmwareVersion piVersion,
FirmwareVersion pmVersion, int lot, int tid, int packetNumber, int messageNumber) {
super(address, messageNumber, packetNumber);
if (timeZone == null) {
@ -57,7 +57,6 @@ public class PodSessionState extends PodState {
this.timeZone = timeZone;
this.setupProgress = SetupProgress.ADDRESS_ASSIGNED;
this.activatedAt = activatedAt;
this.piVersion = piVersion;
this.pmVersion = pmVersion;
this.lot = lot;

View file

@ -1,93 +0,0 @@
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, null).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

@ -1,67 +0,0 @@
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.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 info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst;
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(OmnipodConst.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
}