Decrease I/O load and number of events during command exchange; remove queue status from Omnipod tab and switch reservoir & units delivered; small bug fixes
This commit is contained in:
parent
c84798e368
commit
ae4b25af89
|
@ -76,6 +76,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.communication.mess
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPodStateActionsAllowedChanged;
|
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPodStateActionsAllowedChanged;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.rileylink.RileyLinkOmnipodService;
|
import info.nightscout.androidaps.plugins.pump.omnipod.rileylink.RileyLinkOmnipodService;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.ui.OmnipodFragment;
|
import info.nightscout.androidaps.plugins.pump.omnipod.ui.OmnipodFragment;
|
||||||
|
@ -429,10 +430,10 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AcknowledgeAlerts:
|
case AcknowledgeAlerts:
|
||||||
executeCommand(OmnipodCommandType.GetPodPulseLog, aapsOmnipodManager::acknowledgeAlerts);
|
executeCommand(OmnipodCommandType.AcknowledgeAlerts, aapsOmnipodManager::acknowledgeAlerts);
|
||||||
break;
|
break;
|
||||||
case GetPodState:
|
case GetPodState:
|
||||||
executeCommand(OmnipodCommandType.GetPodPulseLog, aapsOmnipodManager::getPodStatus);
|
executeCommand(OmnipodCommandType.GetPodStatus, aapsOmnipodManager::getPodStatus);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
aapsLogger.error(LTag.PUMP, "Unknown status request: " + statusRequest.name());
|
aapsLogger.error(LTag.PUMP, "Unknown status request: " + statusRequest.name());
|
||||||
|
@ -882,6 +883,8 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface,
|
||||||
// TODO maybe only do this for specific commands
|
// TODO maybe only do this for specific commands
|
||||||
rxBus.send(new EventRefreshOverview("Omnipod command: " + commandType.name(), false));
|
rxBus.send(new EventRefreshOverview("Omnipod command: " + commandType.name(), false));
|
||||||
|
|
||||||
|
rxBus.send(new EventOmnipodPumpValuesChanged());
|
||||||
|
|
||||||
return pumpEnactResult;
|
return pumpEnactResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ public abstract class PodStateManager {
|
||||||
public final void removeState() {
|
public final void removeState() {
|
||||||
this.podState = null;
|
this.podState = null;
|
||||||
storePodState();
|
storePodState();
|
||||||
notifyPodStateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void initState(int address) {
|
public final void initState(int address) {
|
||||||
|
@ -55,7 +54,6 @@ public abstract class PodStateManager {
|
||||||
}
|
}
|
||||||
podState = new PodState(address);
|
podState = new PodState(address);
|
||||||
storePodState();
|
storePodState();
|
||||||
notifyPodStateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,9 +96,6 @@ public abstract class PodStateManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setInitializationParameters(int lot, int tid, FirmwareVersion piVersion, FirmwareVersion pmVersion, DateTimeZone timeZone, PodProgressStatus podProgressStatus) {
|
public final void setInitializationParameters(int lot, int tid, FirmwareVersion piVersion, FirmwareVersion pmVersion, DateTimeZone timeZone, PodProgressStatus podProgressStatus) {
|
||||||
if (!hasPodState()) {
|
|
||||||
throw new IllegalStateException("Cannot set pairing parameters: podState is null");
|
|
||||||
}
|
|
||||||
if (isPodInitialized() && getPodProgressStatus().isAfter(PodProgressStatus.REMINDER_INITIALIZED)) {
|
if (isPodInitialized() && getPodProgressStatus().isAfter(PodProgressStatus.REMINDER_INITIALIZED)) {
|
||||||
throw new IllegalStateException("Cannot set pairing parameters: pairing parameters have already been set");
|
throw new IllegalStateException("Cannot set pairing parameters: pairing parameters have already been set");
|
||||||
}
|
}
|
||||||
|
@ -137,26 +132,34 @@ public abstract class PodStateManager {
|
||||||
return getSafe(() -> podState.getMessageNumber());
|
return getSafe(() -> podState.getMessageNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final void setMessageNumber(int messageNumber) {
|
public final void setMessageNumber(int messageNumber) {
|
||||||
setAndStore(() -> podState.setMessageNumber(messageNumber), false);
|
setSafe(() -> podState.setMessageNumber(messageNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getPacketNumber() {
|
public final int getPacketNumber() {
|
||||||
return getSafe(() -> podState.getPacketNumber());
|
return getSafe(() -> podState.getPacketNumber());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setPacketNumber(int packetNumber) {
|
/**
|
||||||
setAndStore(() -> podState.setPacketNumber(packetNumber), false);
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
}
|
*/
|
||||||
|
|
||||||
public final void increaseMessageNumber() {
|
public final void increaseMessageNumber() {
|
||||||
setAndStore(() -> podState.setMessageNumber((podState.getMessageNumber() + 1) & 0b1111), false);
|
setSafe(() -> podState.setMessageNumber((podState.getMessageNumber() + 1) & 0b1111));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final void increasePacketNumber() {
|
public final void increasePacketNumber() {
|
||||||
setAndStore(() -> podState.setPacketNumber((podState.getPacketNumber() + 1) & 0b11111), false);
|
setSafe(() -> podState.setPacketNumber((podState.getPacketNumber() + 1) & 0b11111));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) {
|
public final synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) {
|
||||||
if (!isPodInitialized()) {
|
if (!isPodInitialized()) {
|
||||||
throw new IllegalStateException("Cannot resync nonce: Pod is not paired yet");
|
throw new IllegalStateException("Cannot resync nonce: Pod is not paired yet");
|
||||||
|
@ -169,7 +172,7 @@ public abstract class PodStateManager {
|
||||||
int seed = ((sum & 0xFFFF) ^ syncWord);
|
int seed = ((sum & 0xFFFF) ^ syncWord);
|
||||||
NonceState nonceState = new NonceState(podState.getLot(), podState.getTid(), (byte) (seed & 0xFF));
|
NonceState nonceState = new NonceState(podState.getLot(), podState.getTid(), (byte) (seed & 0xFF));
|
||||||
|
|
||||||
setAndStore(() -> podState.setNonceState(nonceState), false);
|
setSafe(() -> podState.setNonceState(nonceState));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final synchronized int getCurrentNonce() {
|
public final synchronized int getCurrentNonce() {
|
||||||
|
@ -179,27 +182,36 @@ public abstract class PodStateManager {
|
||||||
return podState.getNonceState().getCurrentNonce();
|
return podState.getNonceState().getCurrentNonce();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final synchronized void advanceToNextNonce() {
|
public final synchronized void advanceToNextNonce() {
|
||||||
if (!isPodInitialized()) {
|
if (!isPodInitialized()) {
|
||||||
throw new IllegalStateException("Cannot advance to next nonce: Pod is not paired yet");
|
throw new IllegalStateException("Cannot advance to next nonce: Pod is not paired yet");
|
||||||
}
|
}
|
||||||
setAndStore(() -> podState.getNonceState().advanceToNextNonce(), false);
|
setSafe(() -> podState.getNonceState().advanceToNextNonce());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DateTime getLastSuccessfulCommunication() {
|
public final DateTime getLastSuccessfulCommunication() {
|
||||||
return getSafe(() -> podState.getLastSuccessfulCommunication());
|
return getSafe(() -> podState.getLastSuccessfulCommunication());
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setLastSuccessfulCommunication(DateTime dateTime, boolean notifyPodStateChanged) {
|
/**
|
||||||
setAndStore(() -> podState.setLastSuccessfulCommunication(dateTime), notifyPodStateChanged);
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
|
public final void setLastSuccessfulCommunication(DateTime dateTime) {
|
||||||
|
setSafe(() -> podState.setLastSuccessfulCommunication(dateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DateTime getLastFailedCommunication() {
|
public final DateTime getLastFailedCommunication() {
|
||||||
return getSafe(() -> podState.getLastFailedCommunication());
|
return getSafe(() -> podState.getLastFailedCommunication());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final void setLastFailedCommunication(DateTime dateTime) {
|
public final void setLastFailedCommunication(DateTime dateTime) {
|
||||||
setAndStore(() -> podState.setLastFailedCommunication(dateTime));
|
setSafe(() -> podState.setLastFailedCommunication(dateTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DateTime getLastUpdatedFromStatusResponse() {
|
public final DateTime getLastUpdatedFromStatusResponse() {
|
||||||
|
@ -359,11 +371,11 @@ public abstract class PodStateManager {
|
||||||
return getSafe(() -> podState.getLastDeliveryStatus());
|
return getSafe(() -> podState.getLastDeliveryStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not automatically store pod state in order to decrease I/O load
|
||||||
|
*/
|
||||||
public final void updateFromStatusResponse(StatusResponse statusResponse) {
|
public final void updateFromStatusResponse(StatusResponse statusResponse) {
|
||||||
if (!hasPodState()) {
|
setSafe(() -> {
|
||||||
throw new IllegalStateException("Cannot update from status response: podState is null");
|
|
||||||
}
|
|
||||||
setAndStore(() -> {
|
|
||||||
if (podState.getActivatedAt() == null) {
|
if (podState.getActivatedAt() == null) {
|
||||||
DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive());
|
DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive());
|
||||||
podState.setActivatedAt(activatedAtCalculated);
|
podState.setActivatedAt(activatedAtCalculated);
|
||||||
|
@ -385,23 +397,21 @@ public abstract class PodStateManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndStore(Runnable runnable) {
|
private void setAndStore(Runnable runnable) {
|
||||||
setAndStore(runnable, true);
|
setSafe(runnable);
|
||||||
|
storePodState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAndStore(Runnable runnable, boolean notifyPodStateChanged) {
|
// Not actually "safe" as it throws an Exception, but it prevents NPEs
|
||||||
|
private void setSafe(Runnable runnable) {
|
||||||
if (!hasPodState()) {
|
if (!hasPodState()) {
|
||||||
throw new IllegalStateException("Cannot mutate PodState: podState is null");
|
throw new IllegalStateException("Cannot mutate PodState: podState is null");
|
||||||
}
|
}
|
||||||
runnable.run();
|
runnable.run();
|
||||||
storePodState();
|
|
||||||
if (notifyPodStateChanged) {
|
|
||||||
notifyPodStateChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storePodState() {
|
public void storePodState() {
|
||||||
String podState = gsonInstance.toJson(this.podState);
|
String podState = gsonInstance.toJson(this.podState);
|
||||||
aapsLogger.info(LTag.PUMP, "storePodState: storing podState: " + podState);
|
aapsLogger.debug(LTag.PUMP, "storePodState: storing podState: {}", podState);
|
||||||
storePodState(podState);
|
storePodState(podState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,12 +435,8 @@ public abstract class PodStateManager {
|
||||||
aapsLogger.error(LTag.PUMP, "loadPodState: could not deserialize PodState: " + storedPodState, ex);
|
aapsLogger.error(LTag.PUMP, "loadPodState: could not deserialize PodState: " + storedPodState, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyPodStateChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void notifyPodStateChanged();
|
|
||||||
|
|
||||||
// Not actually "safe" as it throws an Exception, but it prevents NPEs
|
// Not actually "safe" as it throws an Exception, but it prevents NPEs
|
||||||
private <T> T getSafe(Supplier<T> supplier) {
|
private <T> T getSafe(Supplier<T> supplier) {
|
||||||
if (!hasPodState()) {
|
if (!hasPodState()) {
|
||||||
|
|
|
@ -69,6 +69,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.exception.PodRetur
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.OmnipodManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.OmnipodManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.SetupActionResult;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.SetupActionResult;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.rileylink.OmnipodRileyLinkCommunicationManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.rileylink.OmnipodRileyLinkCommunicationManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil;
|
import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil;
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||||
|
@ -317,6 +318,11 @@ public class AapsOmnipodManager {
|
||||||
aapsLogger.error(LTag.PUMP, "Failed to store active bolus to SP", ex);
|
aapsLogger.error(LTag.PUMP, "Failed to store active bolus to SP", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bolus is already updated in Pod state. If this was an SMB, it could be that
|
||||||
|
// the user is looking at the Pod tab right now, so send an extra event
|
||||||
|
// (this is normally done in OmnipodPumpPlugin)
|
||||||
|
sendEvent(new EventOmnipodPumpValuesChanged());
|
||||||
|
|
||||||
// Wait for the bolus to finish
|
// Wait for the bolus to finish
|
||||||
OmnipodManager.BolusDeliveryResult bolusDeliveryResult =
|
OmnipodManager.BolusDeliveryResult bolusDeliveryResult =
|
||||||
bolusCommandResult.getDeliveryResultSubject().blockingGet();
|
bolusCommandResult.getDeliveryResultSubject().blockingGet();
|
||||||
|
|
|
@ -3,23 +3,17 @@ package info.nightscout.androidaps.plugins.pump.omnipod.manager;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import info.nightscout.androidaps.events.Event;
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||||
import info.nightscout.androidaps.logging.LTag;
|
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStorageKeys;
|
import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStorageKeys;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged;
|
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AapsPodStateManager extends PodStateManager {
|
public class AapsPodStateManager extends PodStateManager {
|
||||||
private final AAPSLogger aapsLogger;
|
|
||||||
private final SP sp;
|
private final SP sp;
|
||||||
private final RxBusWrapper rxBus;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AapsPodStateManager(AAPSLogger aapsLogger, SP sp, RxBusWrapper rxBus) {
|
public AapsPodStateManager(AAPSLogger aapsLogger, SP sp) {
|
||||||
super(aapsLogger);
|
super(aapsLogger);
|
||||||
|
|
||||||
if (aapsLogger == null) {
|
if (aapsLogger == null) {
|
||||||
|
@ -28,13 +22,8 @@ public class AapsPodStateManager extends PodStateManager {
|
||||||
if (sp == null) {
|
if (sp == null) {
|
||||||
throw new IllegalArgumentException("sp can not be null");
|
throw new IllegalArgumentException("sp can not be null");
|
||||||
}
|
}
|
||||||
if (rxBus == null) {
|
|
||||||
throw new IllegalArgumentException("rxBus can not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.aapsLogger = aapsLogger;
|
|
||||||
this.sp = sp;
|
this.sp = sp;
|
||||||
this.rxBus = rxBus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,15 +35,4 @@ public class AapsPodStateManager extends PodStateManager {
|
||||||
protected void storePodState(String podState) {
|
protected void storePodState(String podState) {
|
||||||
sp.putString(OmnipodStorageKeys.Prefs.PodState, podState);
|
sp.putString(OmnipodStorageKeys.Prefs.PodState, podState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void notifyPodStateChanged() {
|
|
||||||
aapsLogger.debug(LTag.PUMP, "Pod State changed. Sending events.");
|
|
||||||
|
|
||||||
sendEvent(new EventOmnipodPumpValuesChanged());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendEvent(Event event) {
|
|
||||||
rxBus.send(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,70 +105,67 @@ public class OmnipodRileyLinkCommunicationManager extends RileyLinkCommunication
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodStateManager podStateManager, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride, boolean automaticallyResyncNonce) {
|
public synchronized <T extends MessageBlock> T exchangeMessages(Class<T> responseClass, PodStateManager podStateManager, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride, boolean automaticallyResyncNonce) {
|
||||||
|
aapsLogger.debug(LTag.PUMPCOMM, "Exchanging OmnipodMessage: responseClass={}, podStateManager={}, message={}, addressOverride={}, ackAddressOverride={}, automaticallyResyncNonce={}", //
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Exchanging OmnipodMessage [responseClass={}, podStateManager={}, message={}, addressOverride={}, ackAddressOverride={}, automaticallyResyncNonce={}]: {}", //
|
|
||||||
responseClass.getSimpleName(), podStateManager, message, addressOverride, ackAddressOverride, automaticallyResyncNonce);
|
responseClass.getSimpleName(), podStateManager, message, addressOverride, ackAddressOverride, automaticallyResyncNonce);
|
||||||
|
|
||||||
for (int i = 0; 2 > i; i++) {
|
try {
|
||||||
|
for (int i = 0; 2 > i; i++) {
|
||||||
|
|
||||||
if (podStateManager.isPodInitialized() && message.isNonceResyncable()) {
|
if (podStateManager.isPodInitialized() && message.isNonceResyncable()) {
|
||||||
podStateManager.advanceToNextNonce();
|
podStateManager.advanceToNextNonce();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageBlock responseMessageBlock;
|
MessageBlock responseMessageBlock;
|
||||||
try {
|
try {
|
||||||
responseMessageBlock = transportMessages(podStateManager, message, addressOverride, ackAddressOverride);
|
responseMessageBlock = transportMessages(podStateManager, message, addressOverride, ackAddressOverride);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
aapsLogger.debug(LTag.PUMPCOMM, "Received response from the Pod [responseMessageBlock={}]", responseMessageBlock);
|
aapsLogger.debug(LTag.PUMPCOMM, "Received response from the Pod [responseMessageBlock={}]", responseMessageBlock);
|
||||||
|
|
||||||
boolean isExpectedResponseType = responseClass.isInstance(responseMessageBlock);
|
if (responseMessageBlock instanceof StatusResponse) {
|
||||||
// Set last successful communication before updating from status response to prevent duplicately notifying Pod state changes
|
podStateManager.updateFromStatusResponse((StatusResponse) responseMessageBlock);
|
||||||
// as podStateManager.updateFromStatusResponse() also notifies of Pod state changes.
|
}
|
||||||
if (isExpectedResponseType) {
|
|
||||||
podStateManager.setLastSuccessfulCommunication(DateTime.now(), !(responseMessageBlock instanceof StatusResponse));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseMessageBlock instanceof StatusResponse) {
|
if (responseClass.isInstance(responseMessageBlock)) {
|
||||||
podStateManager.updateFromStatusResponse((StatusResponse) responseMessageBlock);
|
podStateManager.setLastSuccessfulCommunication(DateTime.now());
|
||||||
}
|
return (T) responseMessageBlock;
|
||||||
|
} else {
|
||||||
if (isExpectedResponseType) {
|
if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) {
|
||||||
return (T) responseMessageBlock;
|
ErrorResponse error = (ErrorResponse) responseMessageBlock;
|
||||||
} else {
|
if (error.getErrorResponseCode() == ErrorResponse.ERROR_RESPONSE_CODE_BAD_NONCE) {
|
||||||
if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) {
|
podStateManager.resyncNonce(error.getNonceSearchKey(), message.getSentNonce(), message.getSequenceNumber());
|
||||||
ErrorResponse error = (ErrorResponse) responseMessageBlock;
|
if (automaticallyResyncNonce) {
|
||||||
if (error.getErrorResponseCode() == ErrorResponse.ERROR_RESPONSE_CODE_BAD_NONCE) {
|
aapsLogger.warn(LTag.PUMPCOMM, "Received ErrorResponse 0x14 (Nonce out of sync). Resyncing nonce and retrying to send message as automaticallyResyncNonce=true");
|
||||||
podStateManager.resyncNonce(error.getNonceSearchKey(), message.getSentNonce(), message.getSequenceNumber());
|
message.resyncNonce(podStateManager.getCurrentNonce());
|
||||||
if (automaticallyResyncNonce) {
|
} else {
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Received ErrorResponse 0x14 (Nonce out of sync). Resyncing nonce and retrying to send message as automaticallyResyncNonce=true");
|
aapsLogger.warn(LTag.PUMPCOMM, "Received ErrorResponse 0x14 (Nonce out of sync). Not resyncing nonce as automaticallyResyncNonce=true");
|
||||||
message.resyncNonce(podStateManager.getCurrentNonce());
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
|
throw new NonceOutOfSyncException();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
aapsLogger.warn(LTag.PUMPCOMM, "Received ErrorResponse 0x14 (Nonce out of sync). Not resyncing nonce as automaticallyResyncNonce=true");
|
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
throw new NonceOutOfSyncException();
|
throw new PodReturnedErrorResponseException(error);
|
||||||
}
|
}
|
||||||
|
} else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) {
|
||||||
|
PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo();
|
||||||
|
podStateManager.setFaultEvent(faultEvent);
|
||||||
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
|
throw new PodFaultException(faultEvent);
|
||||||
} else {
|
} else {
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
throw new PodReturnedErrorResponseException(error);
|
throw new IllegalResponseException(responseClass.getSimpleName(), responseMessageBlock.getType());
|
||||||
}
|
}
|
||||||
} else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) {
|
|
||||||
PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo();
|
|
||||||
podStateManager.setFaultEvent(faultEvent);
|
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
|
||||||
throw new PodFaultException(faultEvent);
|
|
||||||
} else {
|
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
|
||||||
throw new IllegalResponseException(responseClass.getSimpleName(), responseMessageBlock.getType());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
podStateManager.setLastFailedCommunication(DateTime.now());
|
podStateManager.setLastFailedCommunication(DateTime.now());
|
||||||
throw new NonceResyncException();
|
throw new NonceResyncException();
|
||||||
|
} finally {
|
||||||
|
podStateManager.storePodState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageBlock transportMessages(PodStateManager podStateManager, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
|
private MessageBlock transportMessages(PodStateManager podStateManager, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) {
|
||||||
|
|
|
@ -243,8 +243,10 @@ class OmnipodFragment : DaggerFragment() {
|
||||||
omnipod_lastbolus.text = "-"
|
omnipod_lastbolus.text = "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = DateTime.now()
|
||||||
|
|
||||||
// base basal rate
|
// base basal rate
|
||||||
omnipod_base_basal_rate.text = resourceHelper.gs(R.string.pump_basebasalrate, omnipodPumpPlugin.model().determineCorrectBasalSize(omnipodPumpPlugin.baseBasalRate))
|
omnipod_base_basal_rate.text = resourceHelper.gs(R.string.pump_basebasalrate, omnipodPumpPlugin.model().determineCorrectBasalSize(podStateManager.basalSchedule.rateAt(Duration(now.withTimeAtStartOfDay(), now))))
|
||||||
|
|
||||||
omnipod_tempbasal.text = activePlugin.activeTreatments
|
omnipod_tempbasal.text = activePlugin.activeTreatments
|
||||||
.getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "-"
|
.getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "-"
|
||||||
|
@ -265,7 +267,7 @@ class OmnipodFragment : DaggerFragment() {
|
||||||
warnColors.setColorInverse(omnipod_reservoir, podStateManager.reservoirLevel, 50.0, 20.0)
|
warnColors.setColorInverse(omnipod_reservoir, podStateManager.reservoirLevel, 50.0, 20.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
omnipod_pod_active_alerts.text = TextUtils.join(System.lineSeparator(), aapsOmnipodUtil.getTranslatedActiveAlerts(podStateManager))
|
omnipod_pod_active_alerts.text = if (podStateManager.hasActiveAlerts()) TextUtils.join(System.lineSeparator(), aapsOmnipodUtil.getTranslatedActiveAlerts(podStateManager)) else "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.size == 0) {
|
if (errors.size == 0) {
|
||||||
|
@ -275,14 +277,6 @@ class OmnipodFragment : DaggerFragment() {
|
||||||
omnipod_errors.text = StringUtils.join(errors, System.lineSeparator())
|
omnipod_errors.text = StringUtils.join(errors, System.lineSeparator())
|
||||||
omnipod_errors.setTextColor(Color.RED)
|
omnipod_errors.setTextColor(Color.RED)
|
||||||
}
|
}
|
||||||
|
|
||||||
val status = commandQueue.spannedStatus()
|
|
||||||
if (status.toString() == "") {
|
|
||||||
omnipod_queue.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
omnipod_queue.visibility = View.VISIBLE
|
|
||||||
omnipod_queue.text = status
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLastConnection() {
|
private fun updateLastConnection() {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.Riley
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.R
|
import info.nightscout.androidaps.plugins.pump.omnipod.R
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.ui.wizard.defs.PodActionType
|
import info.nightscout.androidaps.plugins.pump.omnipod.ui.wizard.defs.PodActionType
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.ui.wizard.model.FullInitPodWizardModel
|
import info.nightscout.androidaps.plugins.pump.omnipod.ui.wizard.model.FullInitPodWizardModel
|
||||||
|
@ -76,6 +77,10 @@ class PodManagementActivity : NoSplashAppCompatActivity() {
|
||||||
.toObservable(EventRileyLinkDeviceStatusChange::class.java)
|
.toObservable(EventRileyLinkDeviceStatusChange::class.java)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ refreshButtons() }, { fabricPrivacy.logException(it) })
|
.subscribe({ refreshButtons() }, { fabricPrivacy.logException(it) })
|
||||||
|
disposables += rxBus
|
||||||
|
.toObservable(EventOmnipodPumpValuesChanged::class.java)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ refreshButtons() }, { fabricPrivacy.logException(it) })
|
||||||
|
|
||||||
refreshButtons()
|
refreshButtons()
|
||||||
}
|
}
|
||||||
|
@ -142,7 +147,7 @@ class PodManagementActivity : NoSplashAppCompatActivity() {
|
||||||
OKDialog.showConfirmation(this,
|
OKDialog.showConfirmation(this,
|
||||||
resourceHelper.gs(R.string.omnipod_cmd_reset_pod_desc), Thread {
|
resourceHelper.gs(R.string.omnipod_cmd_reset_pod_desc), Thread {
|
||||||
aapsOmnipodManager.resetPodStatus()
|
aapsOmnipodManager.resetPodStatus()
|
||||||
refreshButtons()
|
rxBus.send(EventOmnipodPumpValuesChanged())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.android.HasAndroidInjector;
|
import dagger.android.HasAndroidInjector;
|
||||||
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
import info.nightscout.androidaps.interfaces.ProfileFunction;
|
||||||
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.definition.PodInitActionType;
|
import info.nightscout.androidaps.plugins.pump.omnipod.definition.PodInitActionType;
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager;
|
import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,6 +19,7 @@ public class InitPodTask extends AsyncTask<Void, Void, String> {
|
||||||
|
|
||||||
@Inject ProfileFunction profileFunction;
|
@Inject ProfileFunction profileFunction;
|
||||||
@Inject AapsOmnipodManager aapsOmnipodManager;
|
@Inject AapsOmnipodManager aapsOmnipodManager;
|
||||||
|
@Inject RxBusWrapper rxBus;
|
||||||
private InitActionFragment initActionFragment;
|
private InitActionFragment initActionFragment;
|
||||||
|
|
||||||
public InitPodTask(HasAndroidInjector injector, InitActionFragment initActionFragment) {
|
public InitPodTask(HasAndroidInjector injector, InitActionFragment initActionFragment) {
|
||||||
|
@ -49,6 +52,8 @@ public class InitPodTask extends AsyncTask<Void, Void, String> {
|
||||||
initActionFragment.callResult = aapsOmnipodManager.deactivatePod(initActionFragment);
|
initActionFragment.callResult = aapsOmnipodManager.deactivatePod(initActionFragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rxBus.send(new EventOmnipodPumpValuesChanged());
|
||||||
|
|
||||||
return "OK";
|
return "OK";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,13 +320,6 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/omnipod_queue"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text=""
|
|
||||||
android:textAlignment="center" />
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="2dip"
|
android:layout_height="2dip"
|
||||||
|
@ -517,43 +510,6 @@
|
||||||
android:layout_marginBottom="5dp"
|
android:layout_marginBottom="5dp"
|
||||||
android:background="@color/listdelimiter" />
|
android:background="@color/listdelimiter" />
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1.5"
|
|
||||||
android:gravity="end"
|
|
||||||
android:paddingRight="5dp"
|
|
||||||
android:text="@string/omnipod_total_delivered_label"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="5dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:paddingStart="2dp"
|
|
||||||
android:paddingEnd="2dp"
|
|
||||||
android:text=":"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/omnipod_total_delivered"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="start"
|
|
||||||
android:paddingLeft="5dp"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -590,6 +546,50 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="2dip"
|
||||||
|
android:layout_marginLeft="20dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginRight="20dp"
|
||||||
|
android:layout_marginBottom="5dp"
|
||||||
|
android:background="@color/listdelimiter" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1.5"
|
||||||
|
android:gravity="end"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="@string/omnipod_total_delivered_label"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="5dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:paddingStart="2dp"
|
||||||
|
android:paddingEnd="2dp"
|
||||||
|
android:text=":"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/omnipod_total_delivered"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="start"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
|
|
@ -11,7 +11,6 @@ import org.mockito.Mock;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
import info.nightscout.androidaps.logging.AAPSLogger;
|
import info.nightscout.androidaps.logging.AAPSLogger;
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.FirmwareVersion;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.FirmwareVersion;
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus;
|
import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus;
|
||||||
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
import info.nightscout.androidaps.utils.sharedPreferences.SP;
|
||||||
|
@ -22,7 +21,6 @@ import static org.junit.Assert.assertEquals;
|
||||||
public class AapsPodStateManagerTest {
|
public class AapsPodStateManagerTest {
|
||||||
@Mock AAPSLogger aapsLogger;
|
@Mock AAPSLogger aapsLogger;
|
||||||
@Mock SP sp;
|
@Mock SP sp;
|
||||||
RxBusWrapper rxBus = new RxBusWrapper();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void times() {
|
public void times() {
|
||||||
|
@ -33,7 +31,7 @@ public class AapsPodStateManagerTest {
|
||||||
|
|
||||||
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
||||||
|
|
||||||
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp, rxBus);
|
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp);
|
||||||
podStateManager.initState(0x0);
|
podStateManager.initState(0x0);
|
||||||
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
||||||
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
||||||
|
@ -51,7 +49,7 @@ public class AapsPodStateManagerTest {
|
||||||
|
|
||||||
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
||||||
|
|
||||||
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp, rxBus);
|
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp);
|
||||||
podStateManager.initState(0x0);
|
podStateManager.initState(0x0);
|
||||||
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
||||||
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
||||||
|
@ -74,7 +72,7 @@ public class AapsPodStateManagerTest {
|
||||||
|
|
||||||
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
DateTimeUtils.setCurrentMillisFixed(now.getMillis());
|
||||||
|
|
||||||
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp, rxBus);
|
AapsPodStateManager podStateManager = new AapsPodStateManager(aapsLogger, sp);
|
||||||
podStateManager.initState(0x0);
|
podStateManager.initState(0x0);
|
||||||
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
podStateManager.setInitializationParameters(0, 0, new FirmwareVersion(1, 1, 1),
|
||||||
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
new FirmwareVersion(2, 2, 2), timeZone, PodProgressStatus.ABOVE_FIFTY_UNITS);
|
||||||
|
|
Loading…
Reference in a new issue