- kotlin changes almost done, 1 file left

This commit is contained in:
Andy Rozman 2021-04-20 23:23:03 +01:00
parent 6b7efdcb95
commit 08a5c50037
18 changed files with 793 additions and 1061 deletions

View file

@ -1,10 +1,13 @@
package info.nightscout.androidaps.plugins.treatments;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.firebase.analytics.FirebaseAnalytics;
import java.util.List;
import java.util.stream.Collectors;
@ -14,9 +17,11 @@ import javax.inject.Singleton;
import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.activities.ErrorHelperActivity;
import info.nightscout.androidaps.data.DetailedBolusInfo;
import info.nightscout.androidaps.data.ProfileIntervals;
import info.nightscout.androidaps.database.AppRepository;
import info.nightscout.androidaps.database.embedments.InterfaceIDs;
import info.nightscout.androidaps.db.ExtendedBolus;
import info.nightscout.androidaps.db.ProfileSwitch;
import info.nightscout.androidaps.db.Source;
@ -32,12 +37,16 @@ import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.interfaces.ProfileStore;
import info.nightscout.androidaps.interfaces.TreatmentServiceInterface;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.interfaces.UpdateReturn;
import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.general.nsclient.NSUpload;
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData;
import info.nightscout.androidaps.utils.DateUtil;
import info.nightscout.androidaps.utils.FabricPrivacy;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
@ -67,6 +76,10 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
private final ProfileIntervals<ProfileSwitch> profiles = new ProfileIntervals<>();
private final boolean useNewPumpSync = false;
@Inject
public TreatmentsPlugin(
HasAndroidInjector injector,
@ -245,38 +258,48 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
@Deprecated
@Override
public boolean addToHistoryTempBasal(TemporaryBasal tempBasal) {
throw new IllegalStateException("Migrate to new DB");
/*
if (useNewPumpSync) {
throw new IllegalStateException("Migrate to new DB");
} else {
getAapsLogger().error("!!! addToHistoryTempBasal: Need to migrate to new DB");
}
//log.debug("Adding new TemporaryBasal record" + tempBasal.toString());
boolean newRecordCreated = databaseHelper.createOrUpdate(tempBasal);
if (newRecordCreated) {
if (tempBasal.durationInMinutes == 0)
nsUpload.uploadTempBasalEnd(tempBasal.date, false, tempBasal.pumpId);
else if (tempBasal.isAbsolute)
nsUpload.uploadTempBasalStartAbsolute(tempBasal, null);
else
nsUpload.uploadTempBasalStartPercent(tempBasal, profileFunction.getProfile(tempBasal.date));
// if (tempBasal.durationInMinutes == 0)
// nsUpload.uploadTempBasalEnd(tempBasal.date, false, tempBasal.pumpId);
// else if (tempBasal.isAbsolute)
// nsUpload.uploadTempBasalStartAbsolute(tempBasal, null);
// else
// nsUpload.uploadTempBasalStartPercent(tempBasal, profileFunction.getProfile(tempBasal.date));
}
return newRecordCreated;
*/
}
@Deprecated
public TreatmentUpdateReturn createOrUpdateMedtronic(Treatment treatment, boolean fromNightScout) {
throw new IllegalStateException("Migrate to new DB");
/*
if (useNewPumpSync) {
throw new IllegalStateException("Migrate to new DB");
} else {
getAapsLogger().error("!!! createOrUpdateMedtronic: Need to migrate to new DB");
}
UpdateReturn resultRecord = getService().createOrUpdateMedtronic(treatment, fromNightScout);
return new TreatmentUpdateReturn(resultRecord.getSuccess(), resultRecord.getNewRecord());
*/
}
// return true if new record is created
@Deprecated
@Override
public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolean allowUpdate) {
throw new IllegalStateException("Migrate to new DB");
/*
if (useNewPumpSync) {
throw new IllegalStateException("Migrate to new DB");
} else {
getAapsLogger().error("!!! addToHistoryTreatment: Need to migrate to new DB");
}
boolean medtronicPump = activePlugin.getActivePump() instanceof MedtronicPumpPlugin;
getAapsLogger().debug(MedtronicHistoryData.doubleBolusDebug, LTag.DATATREATMENTS, "DoubleBolusDebug: addToHistoryTreatment::isMedtronicPump={} " + medtronicPump);
@ -320,8 +343,8 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
getService().createOrUpdateMedtronic(carbsTreatment, false);
//log.debug("Adding new Treatment record" + carbsTreatment);
}
if (newRecordCreated && detailedBolusInfo.getBolusType() != DetailedBolusInfo.BolusType.PRIMING)
nsUpload.uploadTreatmentRecord(detailedBolusInfo);
// if (newRecordCreated && detailedBolusInfo.getBolusType() != DetailedBolusInfo.BolusType.PRIMING)
// nsUpload.uploadTreatmentRecord(detailedBolusInfo);
if (!allowUpdate && !creatOrUpdateResult.getSuccess()) {
getAapsLogger().error("Treatment could not be added to DB", new Exception());
@ -337,7 +360,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface
}
return newRecordCreated;
*/
}
@Override

View file

@ -12,6 +12,9 @@ android {
versionCode 1
versionName "1.0"
}
dataBinding {
enabled = true
}
}
dependencies {

View file

@ -74,11 +74,11 @@ abstract class PumpPluginAbstract protected constructor(
@JvmField protected var pumpState = PumpDriverState.NotInitialized
@JvmField protected var displayConnectionMessages = false
var pumpType: PumpType? = null
var pumpType: PumpType = PumpType.GENERIC_AAPS
get() = field
set(value) {
field = value
pumpDescription.setPumpDescription(value!!)
pumpDescription.setPumpDescription(value)
}
@ -366,11 +366,11 @@ abstract class PumpPluginAbstract protected constructor(
}
override fun manufacturer(): ManufacturerType {
return pumpType!!.manufacturer!!
return pumpType.manufacturer!!
}
override fun model(): PumpType {
return pumpType!!
return pumpType
}
@ -395,7 +395,7 @@ abstract class PumpPluginAbstract protected constructor(
val temporaryId = generateTempId(detailedBolusInfo.timestamp)
val response = pumpSync.addBolusWithTempId(detailedBolusInfo.timestamp, detailedBolusInfo.insulin,
temporaryId, detailedBolusInfo.bolusType,
pumpType!!, serialNumber())
pumpType, serialNumber())
if (response && writeToInternalHistory) {
driverHistory[temporaryId] = PumpDbEntry(temporaryId, model(), serialNumber(), detailedBolusInfo)
sp.putString(MedtronicConst.Statistics.InternalTemporaryDatabase, gson.toJson(driverHistory))
@ -403,6 +403,7 @@ abstract class PumpPluginAbstract protected constructor(
return response
}
// TODO
protected fun addTemporaryBasalRateWithTempId(temporaryBasal: TemporaryBasal?, b: Boolean) {
// long temporaryId = generateTempId(temporaryBasal.timestamp);
// boolean response = pumpSync.addBolusWithTempId(temporaryBasal.timestamp, detailedBolusInfo.insulin,

View file

@ -210,7 +210,6 @@ class MedtronicFragment : DaggerFragment() {
} ?: "-"
when (medtronicPumpStatus.pumpDeviceState) {
null,
PumpDeviceState.Sleeping -> binding.pumpStatusIcon.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name());
PumpDeviceState.NeverContacted,
PumpDeviceState.WakingUp,

View file

@ -98,7 +98,7 @@ class MedtronicPumpPlugin @Inject constructor(
.preferencesId(R.xml.pref_medtronic)
.description(R.string.description_pump_medtronic), //
PumpType.MEDTRONIC_522_722, // we default to most basic model, correct model from config is loaded later
injector, resourceHelper, aapsLogger, commandQueue, rxBus, activePlugin, sp, context, fabricPrivacy, dateUtil, aapsSchedulers!!, pumpSync!!
injector, resourceHelper, aapsLogger, commandQueue, rxBus, activePlugin, sp, context, fabricPrivacy, dateUtil, aapsSchedulers, pumpSync
), Pump, RileyLinkPumpDevice {
private var rileyLinkMedtronicService: RileyLinkMedtronicService? = null
@ -149,7 +149,7 @@ class MedtronicPumpPlugin @Inject constructor(
}
private val logPrefix: String
private get() = "MedtronicPumpPlugin::"
get() = "MedtronicPumpPlugin::"
override fun initPumpStatusData() {
medtronicPumpStatus.lastConnection = sp.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L)
@ -225,7 +225,7 @@ class MedtronicPumpPlugin @Inject constructor(
// Pump Plugin
private val isServiceSet: Boolean
private get() = rileyLinkMedtronicService != null
get() = rileyLinkMedtronicService != null
override fun getRileyLinkService(): RileyLinkMedtronicService? {
return rileyLinkMedtronicService
@ -313,7 +313,7 @@ class MedtronicPumpPlugin @Inject constructor(
}//
private val isPumpNotReachable: Boolean
private get() {
get() {
val rileyLinkServiceState = rileyLinkServiceData.rileyLinkServiceState
if (rileyLinkServiceState == null) {
aapsLogger.debug(LTag.PUMP, "RileyLink unreachable. RileyLinkServiceState is null.")
@ -908,7 +908,7 @@ class MedtronicPumpPlugin @Inject constructor(
}
private val lastPumpEntryTime: Long
private get() {
get() {
val lastPumpEntryTime = sp.getLong(MedtronicConst.Statistics.LastPumpHistoryEntry, 0L)
return try {
val localDateTime = DateTimeUtil.toLocalDateTime(lastPumpEntryTime)
@ -1085,7 +1085,7 @@ class MedtronicPumpPlugin @Inject constructor(
private fun isProfileValid(basalProfile: BasalProfile): String? {
val stringBuilder = StringBuilder()
if (medtronicPumpStatus.maxBasal == null) return null
for (profileEntry in basalProfile.entries) {
for (profileEntry in basalProfile.getEntries()) {
if (profileEntry.rate > medtronicPumpStatus.maxBasal!!) {
stringBuilder.append(profileEntry.startTime!!.toString("HH:mm"))
stringBuilder.append("=")
@ -1099,7 +1099,7 @@ class MedtronicPumpPlugin @Inject constructor(
val basalProfile = BasalProfile(aapsLogger)
for (i in 0..23) {
val rate = profile.getBasalTimeFromMidnight(i * 60 * 60)
val v = pumpDescription.pumpType.determineCorrectBasalSize(rate)
val v = pumpType.determineCorrectBasalSize(rate)
val basalEntry = BasalProfileEntry(v, i, 0)
basalProfile.addEntry(basalEntry)
}

View file

@ -1,956 +0,0 @@
package info.nightscout.androidaps.plugins.pump.medtronic.comm;
import android.os.SystemClock;
import org.joda.time.LocalDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType;
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask;
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil;
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil;
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RawHistoryPage;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkLongMessageBody;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkShortMessageBody;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.GetHistoryPageCarelinkMessageBody;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.MessageBody;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PacketType;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpAckMessageBody;
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpMessage;
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile;
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO;
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO;
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO;
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair;
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType;
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType;
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus;
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil;
/**
* Original file created by geoff on 5/30/16.
* <p>
* Split into 2 implementations, so that we can split it by target device. - Andy
* This was mostly rewritten from Original version, and lots of commands and
* functionality added.
*/
@Singleton
public class MedtronicCommunicationManager extends RileyLinkCommunicationManager<PumpMessage> {
@Inject MedtronicPumpStatus medtronicPumpStatus;
@Inject MedtronicPumpPlugin medtronicPumpPlugin;
@Inject MedtronicConverter medtronicConverter;
@Inject MedtronicUtil medtronicUtil;
@Inject MedtronicPumpHistoryDecoder medtronicPumpHistoryDecoder;
private final int MAX_COMMAND_TRIES = 3;
private final int DEFAULT_TIMEOUT = 2000;
private final long RILEYLINK_TIMEOUT = 15 * 60 * 1000; // 15 min
private String errorMessage;
private final boolean debugSetCommands = false;
private boolean doWakeUpBeforeCommand = true;
// This empty constructor must be kept, otherwise dagger injection might break!
@Inject
public MedtronicCommunicationManager() {
}
@Inject
public void onInit() {
// we can't do this in the constructor, as sp only gets injected after the constructor has returned
medtronicPumpStatus.setPreviousConnection(sp.getLong(
RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L));
}
@Override
public PumpMessage createResponseMessage(byte[] payload) {
return new PumpMessage(aapsLogger, payload);
}
@Override
public void setPumpDeviceState(PumpDeviceState pumpDeviceState) {
this.medtronicPumpStatus.setPumpDeviceState(pumpDeviceState);
}
public void setDoWakeUpBeforeCommand(boolean doWakeUp) {
this.doWakeUpBeforeCommand = doWakeUp;
}
@Override
public boolean isDeviceReachable() {
return isDeviceReachable(false);
}
/**
* We do actual wakeUp and compare PumpModel with currently selected one. If returned model is
* not Unknown, pump is reachable.
*
* @return
*/
public boolean isDeviceReachable(boolean canPreventTuneUp) {
PumpDeviceState state = medtronicPumpStatus.getPumpDeviceState();
if (state != PumpDeviceState.PumpUnreachable)
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.WakingUp);
for (int retry = 0; retry < 5; retry++) {
aapsLogger.debug(LTag.PUMPCOMM, "isDeviceReachable. Waking pump... " + (retry != 0 ? " (retry " + retry + ")" : ""));
boolean connected = connectToDevice();
if (connected)
return true;
SystemClock.sleep(1000);
}
if (state != PumpDeviceState.PumpUnreachable)
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.PumpUnreachable);
if (!canPreventTuneUp) {
long diff = System.currentTimeMillis() - medtronicPumpStatus.getLastConnection();
if (diff > RILEYLINK_TIMEOUT) {
serviceTaskExecutor.startTask(new WakeAndTuneTask(injector));
}
}
return false;
}
private boolean connectToDevice() {
PumpDeviceState state = medtronicPumpStatus.getPumpDeviceState();
// check connection
byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple
RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(injector, pumpMsgContent), (byte) 0, (byte) 200,
(byte) 0, (byte) 0, 25000, (byte) 0);
aapsLogger.info(LTag.PUMPCOMM, "wakeup: raw response is " + ByteUtil.shortHexString(rfSpyResponse.getRaw()));
if (rfSpyResponse.wasTimeout()) {
aapsLogger.error(LTag.PUMPCOMM, "isDeviceReachable. Failed to find pump (timeout).");
} else if (rfSpyResponse.looksLikeRadioPacket()) {
RadioResponse radioResponse = new RadioResponse(injector);
try {
radioResponse.init(rfSpyResponse.getRaw());
if (radioResponse.isValid()) {
PumpMessage pumpResponse = createResponseMessage(radioResponse.getPayload());
if (!pumpResponse.isValid()) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Response is invalid ! [interrupted=%b, timeout=%b]", rfSpyResponse.wasInterrupted(),
rfSpyResponse.wasTimeout()));
} else {
// radioResponse.rssi;
Object dataResponse = medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), MedtronicCommandType.PumpModel,
pumpResponse.getRawContent());
MedtronicDeviceType pumpModel = (MedtronicDeviceType) dataResponse;
boolean valid = (pumpModel != MedtronicDeviceType.Unknown_Device);
if (medtronicUtil.getMedtronicPumpModel() == null && valid) {
medtronicUtil.setMedtronicPumpModel(pumpModel);
}
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "isDeviceReachable. PumpModel is %s - Valid: %b (rssi=%d)", pumpModel.name(), valid,
radioResponse.rssi));
if (valid) {
if (state == PumpDeviceState.PumpUnreachable)
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.WakingUp);
else
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
rememberLastGoodDeviceCommunicationTime();
return true;
} else {
if (state != PumpDeviceState.PumpUnreachable)
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.PumpUnreachable);
}
}
} else {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to parse radio response: "
+ ByteUtil.shortHexString(rfSpyResponse.getRaw()));
}
} catch (RileyLinkCommunicationException e) {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to decode radio response: "
+ ByteUtil.shortHexString(rfSpyResponse.getRaw()));
}
} else {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Unknown response: " + ByteUtil.shortHexString(rfSpyResponse.getRaw()));
}
return false;
}
@Override
public boolean tryToConnectToDevice() {
return isDeviceReachable(true);
}
private PumpMessage runCommandWithArgs(PumpMessage msg) throws RileyLinkCommunicationException {
if (debugSetCommands)
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: ");
PumpMessage rval;
PumpMessage shortMessage = makePumpMessage(msg.getCommandType(), new CarelinkShortMessageBody(new byte[]{0}));
// look for ack from short message
PumpMessage shortResponse = sendAndListen(shortMessage);
if (shortResponse.getCommandType() == MedtronicCommandType.CommandACK) {
if (debugSetCommands)
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: Got ACK response");
rval = sendAndListen(msg);
if (debugSetCommands)
aapsLogger.debug(LTag.PUMPCOMM, "2nd Response: " + rval);
return rval;
} else {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithArgs: Pump did not ack Attention packet");
return new PumpMessage(aapsLogger, "No ACK after Attention packet.");
}
}
private PumpMessage runCommandWithFrames(MedtronicCommandType commandType, List<List<Byte>> frames)
throws RileyLinkCommunicationException {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: " + commandType.name());
PumpMessage rval = null;
PumpMessage shortMessage = makePumpMessage(commandType, new CarelinkShortMessageBody(new byte[]{0}));
// look for ack from short message
PumpMessage shortResponse = sendAndListen(shortMessage);
if (shortResponse.getCommandType() != MedtronicCommandType.CommandACK) {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ack Attention packet");
return new PumpMessage(aapsLogger, "No ACK after start message.");
} else {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for Attention packet");
}
int frameNr = 1;
for (List<Byte> frame : frames) {
byte[] frameData = medtronicUtil.createByteArray(frame);
// aapsLogger.debug(LTag.PUMPCOMM,"Frame {} data:\n{}", frameNr, ByteUtil.getCompactString(frameData));
PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(frameData));
rval = sendAndListen(msg);
// aapsLogger.debug(LTag.PUMPCOMM,"PumpResponse: " + rval);
if (rval.getCommandType() != MedtronicCommandType.CommandACK) {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ACK frame #" + frameNr);
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Run command with Frames FAILED (command=%s, response=%s)", commandType.name(),
rval.toString()));
return new PumpMessage(aapsLogger, "No ACK after frame #" + frameNr);
} else {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for frame #" + frameNr);
}
frameNr++;
}
return rval;
}
public PumpHistoryResult getPumpHistory(PumpHistoryEntry lastEntry, LocalDateTime targetDate) {
PumpHistoryResult pumpTotalResult = new PumpHistoryResult(aapsLogger, lastEntry, targetDate == null ? null
: DateTimeUtil.toATechDate(targetDate));
if (doWakeUpBeforeCommand)
wakeUp(receiverDeviceAwakeForMinutes, false);
aapsLogger.debug(LTag.PUMPCOMM, "Current command: " + medtronicUtil.getCurrentCommand());
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
boolean doneWithError = false;
for (int pageNumber = 0; pageNumber < 5; pageNumber++) {
RawHistoryPage rawHistoryPage = new RawHistoryPage(aapsLogger);
// wakeUp(receiverDeviceAwakeForMinutes, false);
PumpMessage getHistoryMsg = makePumpMessage(MedtronicCommandType.GetHistoryData,
new GetHistoryPageCarelinkMessageBody(pageNumber));
aapsLogger.info(LTag.PUMPCOMM, "getPumpHistory: Page " + pageNumber);
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData()));
// Ask the pump to transfer history (we get first frame?)
PumpMessage firstResponse = null;
boolean failed = false;
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, null);
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
try {
firstResponse = runCommandWithArgs(getHistoryMsg);
failed = false;
break;
} catch (RileyLinkCommunicationException e) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "First call for PumpHistory failed (retry=%d)", retries));
failed = true;
}
}
if (failed) {
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return pumpTotalResult;
}
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents()));
PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody());
GetHistoryPageCarelinkMessageBody currentResponse = new GetHistoryPageCarelinkMessageBody(firstResponse.getMessageBody().getTxData());
int expectedFrameNum = 1;
boolean done = false;
// while (expectedFrameNum == currentResponse.getFrameNumber()) {
int failures = 0;
while (!done) {
// examine current response for problems.
byte[] frameData = currentResponse.getFrameData();
if ((frameData != null) && (frameData.length > 0)
&& currentResponse.getFrameNumber() == expectedFrameNum) {
// success! got a frame.
if (frameData.length != 64) {
aapsLogger.warn(LTag.PUMPCOMM, "Expected frame of length 64, got frame of length " + frameData.length);
// but append it anyway?
}
// handle successful frame data
rawHistoryPage.appendData(currentResponse.getFrameData());
// RileyLinkMedtronicService.getInstance().announceProgress(((100 / 16) *
// currentResponse.getFrameNumber() + 1));
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber,
currentResponse.getFrameNumber());
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Got frame %d of Page %d", currentResponse.getFrameNumber(), pageNumber));
// Do we need to ask for the next frame?
if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722
expectedFrameNum++;
} else {
done = true; // successful completion
}
} else {
if (frameData == null) {
aapsLogger.error(LTag.PUMPCOMM, "null frame data, retrying");
} else if (currentResponse.getFrameNumber() != expectedFrameNum) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Expected frame number %d, received %d (retrying)", expectedFrameNum,
currentResponse.getFrameNumber()));
} else if (frameData.length == 0) {
aapsLogger.warn(LTag.PUMPCOMM, "Frame has zero length, retrying");
}
failures++;
if (failures == 6) {
aapsLogger.error(LTag.PUMPCOMM,
String.format(Locale.ENGLISH, "getPumpHistory: 6 failures in attempting to download frame %d of page %d, giving up.",
expectedFrameNum, pageNumber));
done = true; // failure completion.
doneWithError = true;
}
}
if (!done) {
// ask for next frame
PumpMessage nextMsg = null;
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
try {
nextMsg = sendAndListen(ackMsg);
break;
} catch (RileyLinkCommunicationException e) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Problem acknowledging frame response. (retry=%d)", retries));
}
}
if (nextMsg != null)
currentResponse = new GetHistoryPageCarelinkMessageBody(nextMsg.getMessageBody().getTxData());
else {
aapsLogger.error(LTag.PUMPCOMM, "We couldn't acknowledge frame from pump, aborting operation.");
}
}
}
if (rawHistoryPage.getLength() != 1024) {
aapsLogger.warn(LTag.PUMPCOMM, "getPumpHistory: short page. Expected length of 1024, found length of "
+ rawHistoryPage.getLength());
doneWithError = true;
}
if (!rawHistoryPage.isChecksumOK()) {
aapsLogger.error(LTag.PUMPCOMM, "getPumpHistory: checksum is wrong");
doneWithError = true;
}
if (doneWithError) {
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return pumpTotalResult;
}
rawHistoryPage.dumpToDebug();
List<PumpHistoryEntry> medtronicHistoryEntries = medtronicPumpHistoryDecoder.processPageAndCreateRecords(rawHistoryPage);
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Found %d history entries.", medtronicHistoryEntries.size()));
pumpTotalResult.addHistoryEntries(medtronicHistoryEntries, pageNumber);
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Search status: Search finished: %b", pumpTotalResult.isSearchFinished()));
if (pumpTotalResult.isSearchFinished()) {
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return pumpTotalResult;
}
}
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return pumpTotalResult;
}
public String getErrorResponse() {
return this.errorMessage;
}
@Override
public byte[] createPumpMessageContent(RLMessageType type) {
switch (type) {
case PowerOn:
return medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.RFPowerOn, //
new byte[]{2, 1, (byte) receiverDeviceAwakeForMinutes}); // maybe this is better FIXME
case ReadSimpleData:
return medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.PumpModel, null);
}
return new byte[0];
}
private PumpMessage makePumpMessage(MedtronicCommandType messageType, byte[] body) {
return makePumpMessage(messageType, body == null ? new CarelinkShortMessageBody()
: new CarelinkShortMessageBody(body));
}
private PumpMessage makePumpMessage(MedtronicCommandType messageType) {
return makePumpMessage(messageType, (byte[]) null);
}
private PumpMessage makePumpMessage(MedtronicCommandType messageType, MessageBody messageBody) {
PumpMessage msg = new PumpMessage(aapsLogger);
msg.init(PacketType.Carelink, rileyLinkServiceData.pumpIDBytes, messageType, messageBody);
return msg;
}
private PumpMessage sendAndGetResponse(MedtronicCommandType commandType) throws RileyLinkCommunicationException {
return sendAndGetResponse(commandType, null, DEFAULT_TIMEOUT);
}
/**
* Main wrapper method for sending data - (for getting responses)
*
* @param commandType
* @param bodyData
* @param timeoutMs
* @return
*/
private PumpMessage sendAndGetResponse(MedtronicCommandType commandType, byte[] bodyData, int timeoutMs)
throws RileyLinkCommunicationException {
// wakeUp
if (doWakeUpBeforeCommand)
wakeUp(receiverDeviceAwakeForMinutes, false);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
// create message
PumpMessage msg;
if (bodyData == null)
msg = makePumpMessage(commandType);
else
msg = makePumpMessage(commandType, bodyData);
// send and wait for response
PumpMessage response = sendAndListen(msg, timeoutMs);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return response;
}
private PumpMessage sendAndListen(PumpMessage msg) throws RileyLinkCommunicationException {
return sendAndListen(msg, 4000); // 2000
}
// All pump communications go through this function.
@Override
protected PumpMessage sendAndListen(PumpMessage msg, int timeout_ms) throws RileyLinkCommunicationException {
return super.sendAndListen(msg, timeout_ms);
}
private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType) {
return sendAndGetResponseWithCheck(commandType, null);
}
private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType, byte[] bodyData) {
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: " + commandType);
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
try {
PumpMessage response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
String check = checkResponseContent(response, commandType.getCommandDescription(), commandType.getExpectedLength());
if (check == null) {
Object dataResponse = medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), commandType, response.getRawContent());
if (dataResponse != null) {
this.errorMessage = null;
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name(), dataResponse));
return dataResponse;
} else {
this.errorMessage = "Error decoding response.";
}
} else {
this.errorMessage = check;
// return null;
}
} catch (RileyLinkCommunicationException e) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
}
}
return null;
}
private <T> T sendAndGetResponseWithCheck(MedtronicCommandType commandType, byte[] bodyData, Class<T> clazz) {
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: " + commandType);
for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) {
try {
PumpMessage response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
String check = checkResponseContent(response, commandType.getCommandDescription(), commandType.getExpectedLength());
if (check == null) {
T dataResponse = (T)medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), commandType, response.getRawContent());
if (dataResponse != null) {
this.errorMessage = null;
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name(), dataResponse));
return dataResponse;
} else {
this.errorMessage = "Error decoding response.";
}
} else {
this.errorMessage = check;
// return null;
}
} catch (RileyLinkCommunicationException e) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
}
}
return null;
}
private String checkResponseContent(PumpMessage response, String method, int expectedLength) {
if (!response.isValid()) {
String responseData = String.format("%s: Invalid response.", method);
aapsLogger.warn(LTag.PUMPCOMM, responseData);
return responseData;
}
byte[] contents = response.getRawContent();
if (contents != null) {
if (contents.length >= expectedLength) {
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Content: %s", method, ByteUtil.shortHexString(contents)));
return null;
} else {
String responseData = String.format(
"%s: Cannot return data. Data is too short [expected=%s, received=%s].", method, ""
+ expectedLength, "" + contents.length);
aapsLogger.warn(LTag.PUMPCOMM, responseData);
return responseData;
}
} else {
String responseData = String.format("%s: Cannot return data. Null response.", method);
aapsLogger.warn(LTag.PUMPCOMM, responseData);
return responseData;
}
}
// PUMP SPECIFIC COMMANDS
public Double getRemainingInsulin() {
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRemainingInsulin);
return responseObject == null ? null : (Double) responseObject;
}
public MedtronicDeviceType getPumpModel() {
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.PumpModel);
return responseObject == null ? null : (MedtronicDeviceType) responseObject;
}
public BasalProfile getBasalProfile() {
// wakeUp
if (doWakeUpBeforeCommand)
wakeUp(receiverDeviceAwakeForMinutes, false);
MedtronicCommandType commandType = MedtronicCommandType.GetBasalProfileSTD;
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: " + commandType);
medtronicUtil.setCurrentCommand(commandType);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Active);
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
try {
// create message
PumpMessage msg;
msg = makePumpMessage(commandType);
// send and wait for response
PumpMessage response = sendAndListen(msg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getRawContent()));
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getMessageBody().getTxData()));
String check = checkResponseContent(response, commandType.getCommandDescription(), 1);
byte[] data = null;
if (check == null) {
data = response.getRawContentOfFrame();
PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody());
while (checkIfWeHaveMoreData(commandType, response, data)) {
response = sendAndListen(ackMsg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries));
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs, HexDump.toHexStringDisplayable(response2.getRawContent()));
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs,
// HexDump.toHexStringDisplayable(response2.getMessageBody().getTxData()));
String check2 = checkResponseContent(response, commandType.getCommandDescription(), 1);
if (check2 == null) {
data = ByteUtil.concat(data, response.getRawContentOfFrame());
} else {
this.errorMessage = check2;
aapsLogger.error(LTag.PUMPCOMM, "Error with response got GetProfile: " + check2);
}
}
} else {
errorMessage = check;
}
BasalProfile basalProfile = (BasalProfile) medtronicConverter.convertResponse(medtronicPumpPlugin.getPumpDescription().getPumpType(), commandType, data);
if (basalProfile != null) {
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name(), basalProfile));
medtronicUtil.setCurrentCommand(null);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return basalProfile;
}
} catch (RileyLinkCommunicationException e) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
}
}
aapsLogger.warn(LTag.PUMPCOMM, "Error reading profile in max retries.");
medtronicUtil.setCurrentCommand(null);
medtronicPumpStatus.setPumpDeviceState(PumpDeviceState.Sleeping);
return null;
}
private boolean checkIfWeHaveMoreData(MedtronicCommandType commandType, PumpMessage response, byte[] data) {
if (commandType == MedtronicCommandType.GetBasalProfileSTD || //
commandType == MedtronicCommandType.GetBasalProfileA || //
commandType == MedtronicCommandType.GetBasalProfileB) {
byte[] responseRaw = response.getRawContentOfFrame();
int last = responseRaw.length - 1;
aapsLogger.debug(LTag.PUMPCOMM, "Length: " + data.length);
if (data.length >= BasalProfile.MAX_RAW_DATA_SIZE) {
return false;
}
if (responseRaw.length < 2) {
return false;
}
return !(responseRaw[last] == 0x00 && responseRaw[last - 1] == 0x00 && responseRaw[last - 2] == 0x00);
}
return false;
}
public ClockDTO getPumpTime() {
ClockDTO clockDTO = new ClockDTO();
clockDTO.setLocalDeviceTime(new LocalDateTime());
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRealTimeClock);
if (responseObject != null) {
clockDTO.setPumpTime((LocalDateTime) responseObject);
return clockDTO;
}
return null;
}
public TempBasalPair getTemporaryBasal() {
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.ReadTemporaryBasal);
return responseObject == null ? null : (TempBasalPair) responseObject;
}
public Map<String, PumpSettingDTO> getPumpSettings() {
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.getSettings(medtronicUtil
.getMedtronicPumpModel()));
return responseObject == null ? null : (Map<String, PumpSettingDTO>) responseObject;
}
public Boolean setBolus(double units) {
aapsLogger.info(LTag.PUMPCOMM, "setBolus: " + units);
return setCommand(MedtronicCommandType.SetBolus, medtronicUtil.getBolusStrokes(units));
}
public boolean setTBR(TempBasalPair tbr) {
aapsLogger.info(LTag.PUMPCOMM, "setTBR: " + tbr.getDescription());
return setCommand(MedtronicCommandType.SetTemporaryBasal, tbr.getAsRawData());
}
public Boolean setPumpTime() {
GregorianCalendar gc = new GregorianCalendar();
gc.add(Calendar.SECOND, 5);
aapsLogger.info(LTag.PUMPCOMM, "setPumpTime: " + DateTimeUtil.toString(gc));
int i = 1;
byte[] data = new byte[8];
data[0] = 7;
data[i] = (byte) gc.get(Calendar.HOUR_OF_DAY);
data[i + 1] = (byte) gc.get(Calendar.MINUTE);
data[i + 2] = (byte) gc.get(Calendar.SECOND);
byte[] yearByte = MedtronicUtil.getByteArrayFromUnsignedShort(gc.get(Calendar.YEAR), true);
data[i + 3] = yearByte[0];
data[i + 4] = yearByte[1];
data[i + 5] = (byte) (gc.get(Calendar.MONTH) + 1);
data[i + 6] = (byte) gc.get(Calendar.DAY_OF_MONTH);
//aapsLogger.info(LTag.PUMPCOMM,"setPumpTime: Body: " + ByteUtil.getHex(data));
return setCommand(MedtronicCommandType.SetRealTimeClock, data);
}
private boolean setCommand(MedtronicCommandType commandType, byte[] body) {
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
try {
if (this.doWakeUpBeforeCommand)
wakeUp(false);
if (debugSetCommands)
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Body - %s", commandType.getCommandDescription(),
ByteUtil.getHex(body)));
PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(body));
PumpMessage pumpMessage = runCommandWithArgs(msg);
if (debugSetCommands)
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: %s", commandType.getCommandDescription(), pumpMessage.getResponseContent()));
if (pumpMessage.getCommandType() == MedtronicCommandType.CommandACK) {
return true;
} else {
aapsLogger.warn(LTag.PUMPCOMM, "We received non-ACK response from pump: " + pumpMessage.getResponseContent());
}
} catch (RileyLinkCommunicationException e) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
}
}
return false;
}
public boolean cancelTBR() {
return setTBR(new TempBasalPair(0.0d, false, 0));
}
public BatteryStatusDTO getRemainingBattery() {
Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetBatteryStatus);
return responseObject == null ? null : (BatteryStatusDTO) responseObject;
}
public Boolean setBasalProfile(BasalProfile basalProfile) {
List<List<Byte>> basalProfileFrames = medtronicUtil.getBasalProfileFrames(basalProfile.getRawData());
for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) {
PumpMessage responseMessage = null;
try {
responseMessage = runCommandWithFrames(MedtronicCommandType.SetBasalProfileSTD,
basalProfileFrames);
if (responseMessage.getCommandType() == MedtronicCommandType.CommandACK)
return true;
} catch (RileyLinkCommunicationException e) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.getMessage(), retries + 1));
}
if (responseMessage != null)
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Set Basal Profile: Invalid response: commandType=%s,rawData=%s", responseMessage.getCommandType(), ByteUtil.shortHexString(responseMessage.getRawContent())));
else
aapsLogger.warn(LTag.PUMPCOMM, "Set Basal Profile: Null response.");
}
return false;
}
}

View file

@ -0,0 +1,664 @@
package info.nightscout.androidaps.plugins.pump.medtronic.comm
import android.os.SystemClock
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.common.defs.PumpDeviceState
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType
import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask
import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil
import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil
import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RawHistoryPage
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry
import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult
import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.*
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO
import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType.Companion.getSettings
import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType
import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.Companion.createByteArray
import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.Companion.getByteArrayFromUnsignedShort
import org.joda.time.LocalDateTime
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.jvm.Throws
/**
* Original file created by geoff on 5/30/16.
*
*
* Split into 2 implementations, so that we can split it by target device. - Andy
* This was mostly rewritten from Original version, and lots of commands and
* functionality added.
*/
@Singleton
class MedtronicCommunicationManager // This empty constructor must be kept, otherwise dagger injection might break!
@Inject constructor() : RileyLinkCommunicationManager<PumpMessage?>() {
@Inject lateinit var medtronicPumpStatus: MedtronicPumpStatus
@Inject lateinit var medtronicPumpPlugin: MedtronicPumpPlugin
@Inject lateinit var medtronicConverter: MedtronicConverter
@Inject lateinit var medtronicUtil: MedtronicUtil
@Inject lateinit var medtronicPumpHistoryDecoder: MedtronicPumpHistoryDecoder
private val MAX_COMMAND_TRIES = 3
private val DEFAULT_TIMEOUT = 2000
private val RILEYLINK_TIMEOUT: Long = 15 * 60 * 1000 // 15 min
var errorResponse: String? = null
private set
private val debugSetCommands = false
private var doWakeUpBeforeCommand = true
@Inject
open fun onInit(): Unit {
// we can't do this in the constructor, as sp only gets injected after the constructor has returned
medtronicPumpStatus.previousConnection = sp.getLong(
RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L)
}
override fun createResponseMessage(payload: ByteArray): PumpMessage {
return PumpMessage(aapsLogger, payload)
}
override fun setPumpDeviceState(pumpDeviceState: PumpDeviceState) {
medtronicPumpStatus.pumpDeviceState = pumpDeviceState
}
fun setDoWakeUpBeforeCommand(doWakeUp: Boolean) {
doWakeUpBeforeCommand = doWakeUp
}
override fun isDeviceReachable(): Boolean {
return isDeviceReachable(false)
}
/**
* We do actual wakeUp and compare PumpModel with currently selected one. If returned model is
* not Unknown, pump is reachable.
*
* @return
*/
fun isDeviceReachable(canPreventTuneUp: Boolean): Boolean {
val state = medtronicPumpStatus.pumpDeviceState
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.WakingUp
for (retry in 0..4) {
aapsLogger.debug(LTag.PUMPCOMM, "isDeviceReachable. Waking pump... " + if (retry != 0) " (retry $retry)" else "")
val connected = connectToDevice()
if (connected) return true
SystemClock.sleep(1000)
}
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.PumpUnreachable
if (!canPreventTuneUp) {
val diff = System.currentTimeMillis() - medtronicPumpStatus.lastConnection
if (diff > RILEYLINK_TIMEOUT) {
serviceTaskExecutor.startTask(WakeAndTuneTask(injector))
}
}
return false
}
private fun connectToDevice(): Boolean {
val state = medtronicPumpStatus.pumpDeviceState
// check connection
val pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData) // simple
val rfSpyResponse = rfspy.transmitThenReceive(RadioPacket(injector, pumpMsgContent), 0.toByte(), 200.toByte(),
0.toByte(), 0.toByte(), 25000, 0.toByte())
aapsLogger.info(LTag.PUMPCOMM, "wakeup: raw response is " + ByteUtil.shortHexString(rfSpyResponse.raw))
if (rfSpyResponse.wasTimeout()) {
aapsLogger.error(LTag.PUMPCOMM, "isDeviceReachable. Failed to find pump (timeout).")
} else if (rfSpyResponse.looksLikeRadioPacket()) {
val radioResponse = RadioResponse(injector)
try {
radioResponse.init(rfSpyResponse.raw)
if (radioResponse.isValid) {
val pumpResponse = createResponseMessage(radioResponse.payload)
if (!pumpResponse.isValid) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Response is invalid ! [interrupted=%b, timeout=%b]", rfSpyResponse.wasInterrupted(),
rfSpyResponse.wasTimeout()))
} else {
// radioResponse.rssi;
val dataResponse = medtronicConverter!!.convertResponse(medtronicPumpStatus.pumpType, MedtronicCommandType.PumpModel,
pumpResponse.rawContent)
val pumpModel = dataResponse as MedtronicDeviceType?
val valid = pumpModel !== MedtronicDeviceType.Unknown_Device
if (medtronicUtil.medtronicPumpModel == null && valid) {
medtronicUtil.medtronicPumpModel = pumpModel
}
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "isDeviceReachable. PumpModel is %s - Valid: %b (rssi=%d)", pumpModel!!.name, valid,
radioResponse.rssi))
if (valid) {
if (state === PumpDeviceState.PumpUnreachable)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.WakingUp
else
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
rememberLastGoodDeviceCommunicationTime()
return true
} else {
if (state !== PumpDeviceState.PumpUnreachable) medtronicPumpStatus.pumpDeviceState = PumpDeviceState.PumpUnreachable
}
}
} else {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to parse radio response: "
+ ByteUtil.shortHexString(rfSpyResponse.raw))
}
} catch (e: RileyLinkCommunicationException) {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Failed to decode radio response: "
+ ByteUtil.shortHexString(rfSpyResponse.raw))
}
} else {
aapsLogger.warn(LTag.PUMPCOMM, "isDeviceReachable. Unknown response: " + ByteUtil.shortHexString(rfSpyResponse.raw))
}
return false
}
override fun tryToConnectToDevice(): Boolean {
return isDeviceReachable(true)
}
@Throws(RileyLinkCommunicationException::class)
private fun runCommandWithArgs(msg: PumpMessage): PumpMessage {
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: ")
val rval: PumpMessage
val shortMessage = makePumpMessage(msg.commandType, CarelinkShortMessageBody(byteArrayOf(0)))
// look for ack from short message
val shortResponse = sendAndListen(shortMessage)
return if (shortResponse.commandType === MedtronicCommandType.CommandACK) {
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "Run command with Args: Got ACK response")
rval = sendAndListen(msg)
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, "2nd Response: $rval")
rval
} else {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithArgs: Pump did not ack Attention packet")
PumpMessage(aapsLogger, "No ACK after Attention packet.")
}
}
@Throws(RileyLinkCommunicationException::class)
private fun runCommandWithFrames(commandType: MedtronicCommandType, frames: List<List<Byte>>): PumpMessage? {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: " + commandType.name)
var rval: PumpMessage? = null
val shortMessage = makePumpMessage(commandType, CarelinkShortMessageBody(byteArrayOf(0)))
// look for ack from short message
val shortResponse = sendAndListen(shortMessage)
if (shortResponse.commandType !== MedtronicCommandType.CommandACK) {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ack Attention packet")
return PumpMessage(aapsLogger, "No ACK after start message.")
} else {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for Attention packet")
}
var frameNr = 1
for (frame in frames) {
val frameData = createByteArray(frame)
// aapsLogger.debug(LTag.PUMPCOMM,"Frame {} data:\n{}", frameNr, ByteUtil.getCompactString(frameData));
val msg = makePumpMessage(commandType, CarelinkLongMessageBody(frameData))
rval = sendAndListen(msg)
// aapsLogger.debug(LTag.PUMPCOMM,"PumpResponse: " + rval);
if (rval.commandType !== MedtronicCommandType.CommandACK) {
aapsLogger.error(LTag.PUMPCOMM, "runCommandWithFrames: Pump did not ACK frame #$frameNr")
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Run command with Frames FAILED (command=%s, response=%s)", commandType.name,
rval.toString()))
return PumpMessage(aapsLogger, "No ACK after frame #$frameNr")
} else {
aapsLogger.debug(LTag.PUMPCOMM, "Run command with Frames: Got ACK response for frame #$frameNr")
}
frameNr++
}
return rval
}
fun getPumpHistory(lastEntry: PumpHistoryEntry?, targetDate: LocalDateTime?): PumpHistoryResult {
val pumpTotalResult = PumpHistoryResult(aapsLogger, lastEntry, if (targetDate == null) null else DateTimeUtil.toATechDate(targetDate))
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
aapsLogger.debug(LTag.PUMPCOMM, "Current command: " + medtronicUtil.getCurrentCommand())
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
var doneWithError = false
for (pageNumber in 0..4) {
val rawHistoryPage = RawHistoryPage(aapsLogger)
// wakeUp(receiverDeviceAwakeForMinutes, false);
val getHistoryMsg = makePumpMessage(MedtronicCommandType.GetHistoryData,
GetHistoryPageCarelinkMessageBody(pageNumber))
aapsLogger.info(LTag.PUMPCOMM, "getPumpHistory: Page $pageNumber")
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData()));
// Ask the pump to transfer history (we get first frame?)
var firstResponse: PumpMessage? = null
var failed = false
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, null)
for (retries in 0 until MAX_COMMAND_TRIES) {
try {
firstResponse = runCommandWithArgs(getHistoryMsg)
failed = false
break
} catch (e: RileyLinkCommunicationException) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "First call for PumpHistory failed (retry=%d)", retries))
failed = true
}
}
if (failed) {
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return pumpTotalResult
}
// aapsLogger.info(LTag.PUMPCOMM,"getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents()));
val ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, PumpAckMessageBody())
var currentResponse = GetHistoryPageCarelinkMessageBody(firstResponse!!.messageBody!!.txData)
var expectedFrameNum = 1
var done = false
// while (expectedFrameNum == currentResponse.getFrameNumber()) {
var failures = 0
while (!done) {
// examine current response for problems.
val frameData = currentResponse.frameData
if (frameData != null && frameData.size > 0
&& currentResponse.frameNumber == expectedFrameNum) {
// success! got a frame.
if (frameData.size != 64) {
aapsLogger.warn(LTag.PUMPCOMM, "Expected frame of length 64, got frame of length " + frameData.size)
// but append it anyway?
}
// handle successful frame data
rawHistoryPage.appendData(currentResponse.frameData)
// RileyLinkMedtronicService.getInstance().announceProgress(((100 / 16) *
// currentResponse.getFrameNumber() + 1));
medtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber,
currentResponse.frameNumber)
aapsLogger.info(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Got frame %d of Page %d", currentResponse.frameNumber, pageNumber))
// Do we need to ask for the next frame?
if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722
expectedFrameNum++
} else {
done = true // successful completion
}
} else {
if (frameData == null) {
aapsLogger.error(LTag.PUMPCOMM, "null frame data, retrying")
} else if (currentResponse.frameNumber != expectedFrameNum) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Expected frame number %d, received %d (retrying)", expectedFrameNum,
currentResponse.frameNumber))
} else if (frameData.size == 0) {
aapsLogger.warn(LTag.PUMPCOMM, "Frame has zero length, retrying")
}
failures++
if (failures == 6) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: 6 failures in attempting to download frame %d of page %d, giving up.",
expectedFrameNum, pageNumber))
done = true // failure completion.
doneWithError = true
}
}
if (!done) {
// ask for next frame
var nextMsg: PumpMessage? = null
for (retries in 0 until MAX_COMMAND_TRIES) {
try {
nextMsg = sendAndListen(ackMsg)
break
} catch (e: RileyLinkCommunicationException) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Problem acknowledging frame response. (retry=%d)", retries))
}
}
if (nextMsg != null) currentResponse = GetHistoryPageCarelinkMessageBody(nextMsg.messageBody!!.txData) else {
aapsLogger.error(LTag.PUMPCOMM, "We couldn't acknowledge frame from pump, aborting operation.")
}
}
}
if (rawHistoryPage.length != 1024) {
aapsLogger.warn(LTag.PUMPCOMM, "getPumpHistory: short page. Expected length of 1024, found length of "
+ rawHistoryPage.length)
doneWithError = true
}
if (!rawHistoryPage.isChecksumOK) {
aapsLogger.error(LTag.PUMPCOMM, "getPumpHistory: checksum is wrong")
doneWithError = true
}
if (doneWithError) {
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return pumpTotalResult
}
rawHistoryPage.dumpToDebug()
val medtronicHistoryEntries = medtronicPumpHistoryDecoder.processPageAndCreateRecords(rawHistoryPage)
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Found %d history entries.", medtronicHistoryEntries.size))
pumpTotalResult.addHistoryEntries(medtronicHistoryEntries, pageNumber)
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getPumpHistory: Search status: Search finished: %b", pumpTotalResult.isSearchFinished))
if (pumpTotalResult.isSearchFinished) {
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return pumpTotalResult
}
}
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return pumpTotalResult
}
override fun createPumpMessageContent(type: RLMessageType): ByteArray {
return when (type) {
RLMessageType.PowerOn -> medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.RFPowerOn, byteArrayOf(2, 1, receiverDeviceAwakeForMinutes.toByte())) // maybe this is better FIXME
RLMessageType.ReadSimpleData -> medtronicUtil.buildCommandPayload(rileyLinkServiceData, MedtronicCommandType.PumpModel, null)
}
return ByteArray(0)
}
private fun makePumpMessage(messageType: MedtronicCommandType, body: ByteArray? = null as ByteArray?): PumpMessage {
return makePumpMessage(messageType, body?.let { CarelinkShortMessageBody(it) }
?: CarelinkShortMessageBody())
}
private fun makePumpMessage(messageType: MedtronicCommandType?, messageBody: MessageBody): PumpMessage {
val msg = PumpMessage(aapsLogger)
msg.init(PacketType.Carelink, rileyLinkServiceData.pumpIDBytes, messageType, messageBody)
return msg
}
/**
* Main wrapper method for sending data - (for getting responses)
*
* @param commandType
* @param bodyData
* @param timeoutMs
* @return
*/
@Throws(RileyLinkCommunicationException::class)
private fun sendAndGetResponse(commandType: MedtronicCommandType, bodyData: ByteArray? = null, timeoutMs: Int = DEFAULT_TIMEOUT): PumpMessage {
// wakeUp
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
// create message
val msg: PumpMessage
msg = bodyData?.let { makePumpMessage(commandType, it) } ?: makePumpMessage(commandType)
// send and wait for response
val response = sendAndListen(msg, timeoutMs)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return response
}
@Throws(RileyLinkCommunicationException::class)
private fun sendAndListen(msg: PumpMessage): PumpMessage {
return sendAndListen(msg, 4000) // 2000
}
// All pump communications go through this function.
@Throws(RileyLinkCommunicationException::class)
protected /*override*/ fun sendAndListen(msg: PumpMessage, timeout_ms: Int): PumpMessage {
return super.sendAndListen(msg, timeout_ms)!!
}
private fun sendAndGetResponseWithCheck(commandType: MedtronicCommandType, bodyData: ByteArray? = null): Any? {
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: $commandType")
for (retries in 0 until MAX_COMMAND_TRIES) {
try {
val response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
val check = checkResponseContent(response, commandType.commandDescription, commandType.expectedLength)
if (check == null) {
val dataResponse = medtronicConverter.convertResponse(medtronicPumpStatus.pumpType, commandType, response.rawContent)
if (dataResponse != null) {
errorResponse = null
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name, dataResponse))
return dataResponse
} else {
errorResponse = "Error decoding response."
}
} else {
errorResponse = check
// return null;
}
} catch (e: RileyLinkCommunicationException) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
}
}
return null
}
// private fun <T> sendAndGetResponseWithCheck(commandType: MedtronicCommandType, bodyData: ByteArray, clazz: Class<T>): T? {
// aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: $commandType")
// for (retries in 0 until MAX_COMMAND_TRIES) {
// try {
// val response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
// val check = checkResponseContent(response, commandType.commandDescription, commandType.expectedLength)
// if (check == null) {
// val dataResponse = medtronicConverter!!.convertResponse(medtronicPumpPlugin!!.pumpDescription.pumpType, commandType, response.rawContent) as T?
// if (dataResponse != null) {
// errorResponse = null
// aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name, dataResponse))
// return dataResponse
// } else {
// errorResponse = "Error decoding response."
// }
// } else {
// errorResponse = check
// // return null;
// }
// } catch (e: RileyLinkCommunicationException) {
// aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
// }
// }
// return null
// }
private fun checkResponseContent(response: PumpMessage, method: String, expectedLength: Int): String? {
if (!response.isValid) {
val responseData = String.format("%s: Invalid response.", method)
aapsLogger.warn(LTag.PUMPCOMM, responseData)
return responseData
}
val contents = response.rawContent
return if (contents != null) {
if (contents.size >= expectedLength) {
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Content: %s", method, ByteUtil.shortHexString(contents)))
null
} else {
val responseData = String.format(
"%s: Cannot return data. Data is too short [expected=%s, received=%s].", method, ""
+ expectedLength, "" + contents.size)
aapsLogger.warn(LTag.PUMPCOMM, responseData)
responseData
}
} else {
val responseData = String.format("%s: Cannot return data. Null response.", method)
aapsLogger.warn(LTag.PUMPCOMM, responseData)
responseData
}
}
// PUMP SPECIFIC COMMANDS
fun getRemainingInsulin(): Double? {
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRemainingInsulin)
return if (responseObject == null) null else responseObject as Double?
}
fun getPumpModel(): MedtronicDeviceType? {
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.PumpModel)
return if (responseObject == null) null else responseObject as MedtronicDeviceType?
}
fun getBasalProfile(): BasalProfile? {
// wakeUp
if (doWakeUpBeforeCommand) wakeUp(receiverDeviceAwakeForMinutes, false)
val commandType = MedtronicCommandType.GetBasalProfileSTD
aapsLogger.debug(LTag.PUMPCOMM, "getDataFromPump: $commandType")
medtronicUtil.setCurrentCommand(commandType)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Active
for (retries in 0..MAX_COMMAND_TRIES) {
try {
// create message
var msg: PumpMessage
msg = makePumpMessage(commandType)
// send and wait for response
var response = sendAndListen(msg, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getRawContent()));
// aapsLogger.debug(LTag.PUMPCOMM,"1st Response: " + HexDump.toHexStringDisplayable(response.getMessageBody().getTxData()));
val check = checkResponseContent(response, commandType.commandDescription, 1)
var data: ByteArray? = null
if (check == null) {
data = response.rawContentOfFrame
val ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, PumpAckMessageBody())
while (checkIfWeHaveMoreData(commandType, response, data)) {
response = sendAndListen(ackMsg, DEFAULT_TIMEOUT + DEFAULT_TIMEOUT * retries)
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs, HexDump.toHexStringDisplayable(response2.getRawContent()));
// aapsLogger.debug(LTag.PUMPCOMM,"{} Response: {}", runs,
// HexDump.toHexStringDisplayable(response2.getMessageBody().getTxData()));
val check2 = checkResponseContent(response, commandType.commandDescription, 1)
if (check2 == null) {
data = ByteUtil.concat(data, response.rawContentOfFrame)
} else {
errorResponse = check2
aapsLogger.error(LTag.PUMPCOMM, "Error with response got GetProfile: $check2")
}
}
} else {
errorResponse = check
}
val basalProfile = medtronicConverter.convertResponse(medtronicPumpPlugin.pumpType, commandType, data) as BasalProfile?
if (basalProfile != null) {
aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Converted response for %s is %s.", commandType.name, basalProfile))
medtronicUtil.setCurrentCommand(null)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return basalProfile
}
} catch (e: RileyLinkCommunicationException) {
aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
}
}
aapsLogger.warn(LTag.PUMPCOMM, "Error reading profile in max retries.")
medtronicUtil.setCurrentCommand(null)
medtronicPumpStatus.pumpDeviceState = PumpDeviceState.Sleeping
return null
}
private fun checkIfWeHaveMoreData(commandType: MedtronicCommandType, response: PumpMessage, data: ByteArray?): Boolean {
if (commandType === MedtronicCommandType.GetBasalProfileSTD || //
commandType === MedtronicCommandType.GetBasalProfileA || //
commandType === MedtronicCommandType.GetBasalProfileB) {
val responseRaw = response.rawContentOfFrame
val last = responseRaw.size - 1
aapsLogger.debug(LTag.PUMPCOMM, "Length: " + data!!.size)
if (data.size >= BasalProfile.MAX_RAW_DATA_SIZE) {
return false
}
return if (responseRaw.size < 2) {
false
} else !(responseRaw[last] == 0x00.toByte() && responseRaw[last - 1] == 0x00.toByte() && responseRaw[last - 2] == 0x00.toByte())
}
return false
}
fun getPumpTime(): ClockDTO? {
val clockDTO = ClockDTO()
clockDTO.localDeviceTime = LocalDateTime()
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRealTimeClock)
if (responseObject != null) {
clockDTO.pumpTime = responseObject as LocalDateTime?
return clockDTO
}
return null
}
fun getTemporaryBasal(): TempBasalPair? {
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.ReadTemporaryBasal)
return if (responseObject == null) null else responseObject as TempBasalPair?
}
fun getPumpSettings(): Map<String, PumpSettingDTO>? {
val responseObject = sendAndGetResponseWithCheck(getSettings(medtronicUtil.medtronicPumpModel))
return if (responseObject == null) null else responseObject as Map<String, PumpSettingDTO>?
}
fun setBolus(units: Double): Boolean {
aapsLogger.info(LTag.PUMPCOMM, "setBolus: $units")
return setCommand(MedtronicCommandType.SetBolus, medtronicUtil.getBolusStrokes(units))
}
fun setTemporaryBasal(tbr: TempBasalPair): Boolean {
aapsLogger.info(LTag.PUMPCOMM, "setTBR: " + tbr.description)
return setCommand(MedtronicCommandType.SetTemporaryBasal, tbr.asRawData)
}
fun setPumpTime(): Boolean {
val gc = GregorianCalendar()
gc.add(Calendar.SECOND, 5)
aapsLogger.info(LTag.PUMPCOMM, "setPumpTime: " + DateTimeUtil.toString(gc))
val i = 1
val data = ByteArray(8)
data[0] = 7
data[i] = gc[Calendar.HOUR_OF_DAY].toByte()
data[i + 1] = gc[Calendar.MINUTE].toByte()
data[i + 2] = gc[Calendar.SECOND].toByte()
val yearByte = getByteArrayFromUnsignedShort(gc[Calendar.YEAR], true)
data[i + 3] = yearByte[0]
data[i + 4] = yearByte[1]
data[i + 5] = (gc[Calendar.MONTH] + 1).toByte()
data[i + 6] = gc[Calendar.DAY_OF_MONTH].toByte()
//aapsLogger.info(LTag.PUMPCOMM,"setPumpTime: Body: " + ByteUtil.getHex(data));
return setCommand(MedtronicCommandType.SetRealTimeClock, data)
}
private fun setCommand(commandType: MedtronicCommandType, body: ByteArray): Boolean {
for (retries in 0..MAX_COMMAND_TRIES) {
try {
if (doWakeUpBeforeCommand) wakeUp(false)
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: Body - %s", commandType.commandDescription,
ByteUtil.getHex(body)))
val msg = makePumpMessage(commandType, CarelinkLongMessageBody(body))
val pumpMessage = runCommandWithArgs(msg)
if (debugSetCommands) aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "%s: %s", commandType.commandDescription, pumpMessage.responseContent))
if (pumpMessage.commandType === MedtronicCommandType.CommandACK) {
return true
} else {
aapsLogger.warn(LTag.PUMPCOMM, "We received non-ACK response from pump: " + pumpMessage.responseContent)
}
} catch (e: RileyLinkCommunicationException) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
}
}
return false
}
fun cancelTBR(): Boolean {
return setTemporaryBasal(TempBasalPair(0.0, false, 0))
}
fun getRemainingBattery(): BatteryStatusDTO? {
val responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetBatteryStatus)
return if (responseObject == null) null else responseObject as BatteryStatusDTO?
}
fun setBasalProfile(basalProfile: BasalProfile): Boolean {
val basalProfileFrames = medtronicUtil.getBasalProfileFrames(basalProfile.rawData)
for (retries in 0..MAX_COMMAND_TRIES) {
var responseMessage: PumpMessage? = null
try {
responseMessage = runCommandWithFrames(MedtronicCommandType.SetBasalProfileSTD,
basalProfileFrames)
if (responseMessage!!.commandType === MedtronicCommandType.CommandACK) return true
} catch (e: RileyLinkCommunicationException) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error getting response from RileyLink (error=%s, retry=%d)", e.message, retries + 1))
}
if (responseMessage != null) aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Set Basal Profile: Invalid response: commandType=%s,rawData=%s", responseMessage.commandType, ByteUtil.shortHexString(responseMessage.rawContent))) else aapsLogger.warn(LTag.PUMPCOMM, "Set Basal Profile: Null response.")
}
return false
}
}

View file

@ -51,7 +51,7 @@ abstract class MedtronicHistoryDecoder<T : MedtronicHistoryEntry?> : MedtronicHi
}
}
fun processPageAndCreateRecords(rawHistoryPage: RawHistoryPage): List<T?>? {
fun processPageAndCreateRecords(rawHistoryPage: RawHistoryPage): List<T> {
return processPageAndCreateRecords(rawHistoryPage, false)
}
@ -120,7 +120,7 @@ abstract class MedtronicHistoryDecoder<T : MedtronicHistoryEntry?> : MedtronicHi
private fun processPageAndCreateRecords(rawHistoryPage: RawHistoryPage, partial: Boolean): List<T> {
val dataClear = checkPage(rawHistoryPage, partial)
val records: List<T> = createRecords(dataClear)
for (record in records!!) {
for (record in records) {
decodeRecord(record)
}
runPostDecodeTasks()

View file

@ -25,14 +25,14 @@ class MedtronicCGMSHistoryDecoder : MedtronicHistoryDecoder<CGMSHistoryEntry>()
override fun decodeRecord(record: CGMSHistoryEntry): RecordDecodeStatus? {
return try {
decodeRecord(record, false)
decodeRecordInternal(record)
} catch (ex: Exception) {
LOG.error(" Error decoding: type={}, ex={}", record.entryType!!.name, ex.message, ex)
RecordDecodeStatus.Error
}
}
fun decodeRecord(entry: CGMSHistoryEntry, ignore: Boolean): RecordDecodeStatus {
fun decodeRecordInternal(entry: CGMSHistoryEntry): RecordDecodeStatus {
if (entry.dateTimeLength > 0) {
parseDate(entry)
}
@ -62,6 +62,7 @@ class MedtronicCGMSHistoryDecoder : MedtronicHistoryDecoder<CGMSHistoryEntry>()
}
override fun postProcess() {}
override fun createRecords(dataClearInput: List<Byte>): List<CGMSHistoryEntry> {
val dataClear = reverseList(dataClearInput, Byte::class.java)
prepareStatistics()

View file

@ -79,7 +79,7 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
listRawData.add(opCode.toByte())
if (entryType === PumpHistoryEntryType.UnabsorbedInsulin
|| entryType === PumpHistoryEntryType.UnabsorbedInsulin512) {
val elements: Int = dataClear[counter]!!.toInt()
val elements: Int = dataClear[counter].toInt()
listRawData.add(elements.toByte())
counter++
val els = getUnsignedInt(elements)
@ -106,12 +106,12 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
if (incompletePacket) break
}
if (entryType === PumpHistoryEntryType.None) {
aapsLogger!!.error(LTag.PUMPBTCOMM, "Error in code. We should have not come into this branch.")
aapsLogger.error(LTag.PUMPBTCOMM, "Error in code. We should have not come into this branch.")
} else {
if (pe.entryType === PumpHistoryEntryType.UnknownBasePacket) {
pe.opCode = opCode.toByte()
}
if (entryType.getHeadLength(medtronicUtil!!.medtronicPumpModel!!) == 0) special = true
if (entryType.getHeadLength(medtronicUtil.medtronicPumpModel!!) == 0) special = true
pe.setData(listRawData as List<Byte>, special)
val decoded = decodeRecord(pe)
if (decoded === RecordDecodeStatus.OK || decoded === RecordDecodeStatus.Ignored) {
@ -132,7 +132,7 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
override fun decodeRecord(record: PumpHistoryEntry): RecordDecodeStatus? {
return try {
decodeRecord(record, false)
decodeRecordInternal(record)
} catch (ex: Exception) {
aapsLogger.error(LTag.PUMPBTCOMM, String.format(Locale.ENGLISH, " Error decoding: type=%s, ex=%s", record.entryType!!.name, ex.message, ex))
//ex.printStackTrace()
@ -140,7 +140,7 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
}
}
private fun decodeRecord(entry: PumpHistoryEntry, x: Boolean): RecordDecodeStatus {
private fun decodeRecordInternal(entry: PumpHistoryEntry): RecordDecodeStatus {
if (entry.dateTimeLength > 0) {
decodeDateTime(entry)
}
@ -169,6 +169,7 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
PumpHistoryEntryType.TempBasalDuration -> // decodeTempBasal(entry);
RecordDecodeStatus.OK
PumpHistoryEntryType.TempBasalRate -> // decodeTempBasal(entry);
RecordDecodeStatus.OK
@ -254,7 +255,7 @@ class MedtronicPumpHistoryDecoder @Inject constructor(
val body = entry.body!!
val dto = BolusWizardDTO()
var bolusStrokes = 10.0f
if (MedtronicDeviceType.isSameDevice(medtronicUtil!!.medtronicPumpModel!!, MedtronicDeviceType.Medtronic_523andHigher)) {
if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel!!, MedtronicDeviceType.Medtronic_523andHigher)) {
// https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/log_entries/bolus_wizard.rb#L102
bolusStrokes = 40.0f
dto.carbs = ((body[1] and 0x0c.toByte()).toInt() shl 6) + body[0]

View file

@ -69,10 +69,10 @@ class PumpHistoryEntry : MedtronicHistoryEntry() {
override val dateLength: Int
get() = entryType!!.dateLength
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is PumpHistoryEntry) return false
val that = o
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PumpHistoryEntry) return false
val that = other
return entryType == that.entryType && //
atechDateTime === that.atechDateTime // && //
// Objects.equals(this.decodedData, that.decodedData);

View file

@ -81,8 +81,8 @@ class PumpHistoryResult(private val aapsLogger: AAPSLogger, searchEntry: PumpHis
}
override fun toString(): String {
return "PumpHistoryResult [unprocessed=" + (if (unprocessedEntries != null) "" + unprocessedEntries!!.size else "0") + //
", valid=" + (if (validEntries != null) "" + validEntries!!.size else "0") + //
return "PumpHistoryResult [unprocessed=" + unprocessedEntries.size + //
", valid=" + validEntries.size + //
", searchEntry=" + searchEntry + //
", searchDate=" + searchDate + //
", searchType=" + searchType + //

View file

@ -53,19 +53,19 @@ class MedtronicUITask {
aapsLogger.debug(LTag.PUMP, "MedtronicUITask: @@@ In execute. $commandType")
when (commandType) {
MedtronicCommandType.PumpModel -> {
result = communicationManager.pumpModel
result = communicationManager.getPumpModel()
}
MedtronicCommandType.GetBasalProfileSTD -> {
result = communicationManager.basalProfile
result = communicationManager.getBasalProfile()
}
MedtronicCommandType.GetRemainingInsulin -> {
result = communicationManager.remainingInsulin
result = communicationManager.getRemainingInsulin()
}
MedtronicCommandType.GetRealTimeClock -> {
result = communicationManager.pumpTime
result = communicationManager.getPumpTime()
medtronicUtil.pumpTime = null
}
@ -74,22 +74,22 @@ class MedtronicUITask {
}
MedtronicCommandType.GetBatteryStatus -> {
result = communicationManager.remainingBattery
result = communicationManager.getRemainingBattery()
}
MedtronicCommandType.SetTemporaryBasal -> {
val tbr = tBRSettings
val tbr = getTbrSettings()
if (tbr != null) {
result = communicationManager.setTBR(tbr)
result = communicationManager.setTemporaryBasal(tbr)
}
}
MedtronicCommandType.ReadTemporaryBasal -> {
result = communicationManager.temporaryBasal
result = communicationManager.getTemporaryBasal()
}
MedtronicCommandType.Settings, MedtronicCommandType.Settings_512 -> {
result = communicationManager.pumpSettings
result = communicationManager.getPumpSettings()
}
MedtronicCommandType.SetBolus -> {
@ -127,12 +127,11 @@ class MedtronicUITask {
}
}
//
//
private val tBRSettings: TempBasalPair
private get() = TempBasalPair(getDoubleFromParameters(0), //
private fun getTbrSettings(): TempBasalPair? {
return TempBasalPair(getDoubleFromParameters(0), //
false, //
getIntegerFromParameters(1))
}
private fun getFloatFromParameters(index: Int): Float {
return parameters!![index] as Float

View file

@ -96,7 +96,7 @@ class BasalProfile {
fun dumpBasalProfile() {
aapsLogger.debug(LTag.PUMPCOMM, "Basal Profile entries:")
val entries = entries
val entries = getEntries()
for (i in entries.indices) {
val entry = entries[i]
val startString = entry.startTime!!.toString("HH:mm")
@ -109,7 +109,7 @@ class BasalProfile {
val basalProfileAsString: String
get() {
val sb = StringBuffer("Basal Profile entries:\n")
val entries = entries
val entries = getEntries()
for (i in entries.indices) {
val entry = entries[i]
val startString = entry.startTime!!.toString("HH:mm")
@ -124,7 +124,7 @@ class BasalProfile {
fun basalProfileToString(): String {
val sb = StringBuffer("Basal Profile [")
val entries = entries
val entries = getEntries()
for (i in entries.indices) {
val entry = entries[i]
val startString = entry.startTime!!.toString("HH:mm")
@ -138,7 +138,7 @@ class BasalProfile {
// and changes to the profiles themselves.
fun getEntryForTime(`when`: Instant): BasalProfileEntry {
var rval = BasalProfileEntry()
val entries = entries
val entries = getEntries()
if (entries.size == 0) {
aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): table is empty",
`when`.toDateTime().toLocalTime().toString("HH:mm")))
@ -181,31 +181,30 @@ class BasalProfile {
}// readUnsignedByte(mRawData[i]);
// an empty list
val entries: List<BasalProfileEntry>
get() {
val entries: MutableList<BasalProfileEntry> = ArrayList()
if (rawData == null || rawData!![2] == 0x3f.toByte()) {
aapsLogger.warn(LTag.PUMPCOMM, "Raw Data is empty.")
return entries // an empty list
}
var r: Int
var st: Int
var i = 0
while (i < rawData!!.size - 2) {
if (rawData!![i] == 0.toByte() && rawData!![i + 1] == 0.toByte() && rawData!![i + 2] == 0.toByte()) break
if (rawData!![i] == 0.toByte() && rawData!![i + 1] == 0.toByte() && rawData!![i + 2] == 0x3f.toByte()) break
r = MedtronicUtil.makeUnsignedShort(rawData!![i + 1].toInt(), rawData!![i].toInt()) // readUnsignedByte(mRawData[i]);
st = readUnsignedByte(rawData!![i + 2])
try {
entries.add(BasalProfileEntry(aapsLogger, r, st))
} catch (ex: Exception) {
aapsLogger.error(LTag.PUMPCOMM, "Error decoding basal profile from bytes: " + ByteUtil.shortHexString(rawData))
throw ex
}
i += 3
}
return entries
fun getEntries(): List<BasalProfileEntry> {
val entries: MutableList<BasalProfileEntry> = ArrayList()
if (rawData[2] == 0x3f.toByte()) {
aapsLogger.warn(LTag.PUMPCOMM, "Raw Data is empty.")
return entries // an empty list
}
var r: Int
var st: Int
var i = 0
while (i < rawData.size - 2) {
if (rawData[i] == 0.toByte() && rawData[i + 1] == 0.toByte() && rawData[i + 2] == 0.toByte()) break
if (rawData[i] == 0.toByte() && rawData[i + 1] == 0.toByte() && rawData[i + 2] == 0x3f.toByte()) break
r = MedtronicUtil.makeUnsignedShort(rawData[i + 1].toInt(), rawData[i].toInt()) // readUnsignedByte(mRawData[i]);
st = readUnsignedByte(rawData[i + 2])
try {
entries.add(BasalProfileEntry(aapsLogger, r, st))
} catch (ex: Exception) {
aapsLogger.error(LTag.PUMPCOMM, "Error decoding basal profile from bytes: " + ByteUtil.shortHexString(rawData))
throw ex
}
i += 3
}
return entries
}
/**
* This is used to prepare new profile
@ -233,7 +232,7 @@ class BasalProfile {
fun getProfilesByHour(pumpType: PumpType): Array<Double> {
var entriesCopy: List<BasalProfileEntry>? = null
try {
entriesCopy = entries
entriesCopy = getEntries()
} catch (ex: Exception) {
aapsLogger.error(LTag.PUMPCOMM, "=============================================================================")
aapsLogger.error(LTag.PUMPCOMM, " Error generating entries. Ex.: $ex", ex)
@ -280,7 +279,7 @@ class BasalProfile {
fun verify(pumpType: PumpType): Boolean {
try {
entries
getEntries()
} catch (ex: Exception) {
return false
}

View file

@ -38,14 +38,14 @@ class MedtronicHistoryActivity : DaggerActivity() {
var manualChange = false
var typeListFull: List<TypeList>? = null
private var _binding: MedtronicHistoryActivityBinding? = null
//private var _binding: MedtronicHistoryActivityBinding? = null
//@Inject
//var fragmentInjector: DispatchingAndroidInjector<Fragment>? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
//private val binding get() = _binding!!
private fun filterHistory(group: PumpHistoryEntryGroup) {
filteredHistoryList.clear()
@ -93,20 +93,17 @@ class MedtronicHistoryActivity : DaggerActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.medtronic_history_activity)
_binding = MedtronicHistoryActivityBinding.inflate(getLayoutInflater()) //(inflater, container, false)
historyTypeSpinner = binding.medtronicHistorytype //findViewById(R.id.medtronic_historytype)
statusView = binding.medtronicHistorystatus //findViewById(R.id.medtronic_historystatus)
recyclerView = binding.medtronicHistoryRecyclerview //findViewById(R.id.medtronic_history_recyclerview)
setContentView(R.layout.medtronic_history_activity)
historyTypeSpinner = findViewById(R.id.medtronic_historytype)
statusView = findViewById(R.id.medtronic_historystatus)
recyclerView = findViewById(R.id.medtronic_history_recyclerview)
recyclerView.setHasFixedSize(true)
llm = LinearLayoutManager(this)
recyclerView.setLayoutManager(llm)
recyclerViewAdapter = RecyclerViewAdapter(filteredHistoryList)
recyclerView.setAdapter(recyclerViewAdapter)
statusView.setVisibility(View.GONE)
typeListFull = getTypeList(getTranslatedList(resourceHelper!!))
typeListFull = getTypeList(PumpHistoryEntryGroup.getTranslatedList(resourceHelper))
val spinnerAdapter = ArrayAdapter(this, R.layout.spinner_centered, typeListFull)
historyTypeSpinner.setAdapter(spinnerAdapter)
historyTypeSpinner.setOnItemSelectedListener(object : AdapterView.OnItemSelectedListener {
@ -163,11 +160,11 @@ class MedtronicHistoryActivity : DaggerActivity() {
override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) {
val record = historyList[position]
if (record != null) {
//if (record != null) {
holder.timeView.text = record.dateTimeString
holder.typeView.text = record.entryType!!.description
holder.valueView.text = record.displayableValue
}
//}
}
override fun getItemCount(): Int {

View file

@ -108,8 +108,8 @@ class RileyLinkStatusDeviceMedtronic : DaggerFragment(), RefreshableInterface {
return i.toLong()
}
override fun getView(i: Int, view: View, viewGroup: ViewGroup): View {
var view = view
override fun getView(i: Int, viewIn: View, viewGroup: ViewGroup): View {
var view = viewIn
val viewHolder: ViewHolder
// General ListView optimization code.
if (view == null) {

View file

@ -164,6 +164,7 @@ class RileyLinkMedtronicService // This empty constructor must be kept, otherwi
} else {
val pumpType = medtronicPumpStatus.medtronicPumpMap[pumpTypePart]
medtronicPumpStatus.medtronicDeviceType = medtronicPumpStatus.medtronicDeviceTypeMap[pumpTypePart]
medtronicPumpStatus.pumpType = pumpType!!
medtronicPumpPlugin.pumpType = pumpType
if (pumpTypePart.startsWith("7")) medtronicPumpStatus.reservoirFullUnits = 300 else medtronicPumpStatus.reservoirFullUnits = 176
}

View file

@ -291,10 +291,10 @@ class MedtronicUtil @Inject constructor(
}
fun getStrokesInt(amount: Double, strokesPerUnit: Int): Int {
var length = 1
//var length = 1
var scrollRate = 1
if (strokesPerUnit >= 40) {
length = 2
// length = 2
// 40-stroke pumps scroll faster for higher unit values
if (amount > 10) scrollRate = 4 else if (amount > 1) scrollRate = 2