diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt index d9124ec15e..43a75631bf 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt @@ -311,13 +311,21 @@ class MedtronicFragment : DaggerFragment() { binding.lastBolus.text = "" } - val pumpState = pumpSync.expectedPumpState() - // base basal rate - binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") " - + resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate)) + if (true) { + // base basal rate + binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") " + + resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate)) - binding.tempBasal.text = pumpState.temporaryBasal?.toStringFull(dateUtil) - ?: "" + binding.tempBasal.text = "??" + } else { + val pumpState = pumpSync.expectedPumpState() + // base basal rate + binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") " + + resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate)) + + binding.tempBasal.text = pumpState.temporaryBasal?.toStringFull(dateUtil) + ?: "" + } // battery if (medtronicPumpStatus.batteryType == BatteryType.None || medtronicPumpStatus.batteryVoltage == null) { diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java index f41fb04348..b514ec7d3a 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java @@ -1499,10 +1499,10 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements Pump, Ril for (BasalProfileEntry profileEntry : basalProfile.getEntries()) { - if (profileEntry.rate > medtronicPumpStatus.maxBasal) { - stringBuilder.append(profileEntry.startTime.toString("HH:mm")); + if (profileEntry.getRate() > medtronicPumpStatus.maxBasal) { + stringBuilder.append(profileEntry.getStartTime().toString("HH:mm")); stringBuilder.append("="); - stringBuilder.append(profileEntry.rate); + stringBuilder.append(profileEntry.getRate()); } } diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java deleted file mode 100644 index f9ad49b723..0000000000 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java +++ /dev/null @@ -1,444 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.medtronic.comm; - -import org.joda.time.IllegalFieldValueException; -import org.joda.time.LocalDateTime; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; -import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; -import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; -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.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.defs.PumpConfigurationGroup; -import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; - -/** - * Created by andy on 5/9/18. - * High level decoder for data returned through MedtroniUIComm - */ - -@Singleton -public class MedtronicConverter { - - private final AAPSLogger aapsLogger; - private final MedtronicUtil medtronicUtil; - - @Inject - public MedtronicConverter( - AAPSLogger aapsLogger, - MedtronicUtil medtronicUtil - ) { - this.aapsLogger = aapsLogger; - this.medtronicUtil = medtronicUtil; - } - - Object convertResponse(PumpType pumpType, MedtronicCommandType commandType, byte[] rawContent) { - - if ((rawContent == null || rawContent.length < 1) && commandType != MedtronicCommandType.PumpModel) { - aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Content is empty or too short, no data to convert (type=%s,isNull=%b,length=%s)", - commandType.name(), rawContent == null, rawContent == null ? "-" : rawContent.length)); - return null; - } - - aapsLogger.debug(LTag.PUMPCOMM, "Raw response before convert: " + ByteUtil.shortHexString(rawContent)); - - switch (commandType) { - - case PumpModel: { - return decodeModel(rawContent); - } - - case GetRealTimeClock: { - return decodeTime(rawContent); - } - - case GetRemainingInsulin: { - return decodeRemainingInsulin(rawContent); - } - - case GetBatteryStatus: { - return decodeBatteryStatus(rawContent); // 1 - } - - case GetBasalProfileSTD: - case GetBasalProfileA: - case GetBasalProfileB: { - return decodeBasalProfile(pumpType, rawContent); - - } - - case ReadTemporaryBasal: { - return new TempBasalPair(aapsLogger, rawContent); // 5 - } - - case Settings_512: { - return decodeSettingsLoop(rawContent); - } - - case Settings: { - return decodeSettingsLoop(rawContent); - } - - case SetBolus: { - return rawContent; // 1 - } - - default: { - throw new RuntimeException("Unsupported command Type: " + commandType); - } - - } - - } - - - private BasalProfile decodeBasalProfile(PumpType pumpType, byte[] rawContent) { - - BasalProfile basalProfile = new BasalProfile(aapsLogger, rawContent); - - return basalProfile.verify(pumpType) ? basalProfile : null; - } - - - private MedtronicDeviceType decodeModel(byte[] rawContent) { - - if ((rawContent == null || rawContent.length < 4)) { - aapsLogger.warn(LTag.PUMPCOMM, "Error reading PumpModel, returning Unknown_Device"); - return MedtronicDeviceType.Unknown_Device; - } - - String rawModel = StringUtil.fromBytes(ByteUtil.substring(rawContent, 1, 3)); - MedtronicDeviceType pumpModel = MedtronicDeviceType.getByDescription(rawModel); - aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "PumpModel: [raw=%s, resolved=%s]", rawModel, pumpModel.name())); - - if (pumpModel != MedtronicDeviceType.Unknown_Device) { - if (!medtronicUtil.isModelSet()) { - medtronicUtil.setMedtronicPumpModel(pumpModel); - } - } - - return pumpModel; - } - - - private BatteryStatusDTO decodeBatteryStatus(byte[] rawData) { - // 00 7C 00 00 - - BatteryStatusDTO batteryStatus = new BatteryStatusDTO(); - - int status = rawData[0]; - - if (status == 0) { - batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Normal; - } else if (status == 1) { - batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Low; - } else if (status == 2) { - batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Unknown; - } - - if (rawData.length > 1) { - - Double d = null; - - // if response in 3 bytes then we add additional information - if (rawData.length == 2) { - d = (rawData[1] * 1.0d) / 100.0d; - } else { - d = (ByteUtil.toInt(rawData[1], rawData[2]) * 1.0d) / 100.0d; - } - - batteryStatus.voltage = d; - batteryStatus.extendedDataReceived = true; - } - - return batteryStatus; - } - - - private Float decodeRemainingInsulin(byte[] rawData) { - int startIdx = 0; - - MedtronicDeviceType pumpModel = medtronicUtil.getMedtronicPumpModel(); - - int strokes = pumpModel == null ? 10 : pumpModel.getBolusStrokes(); - - if (strokes == 40) { - startIdx = 2; - } - - int reqLength = startIdx + 1; - float value = 0; - - if (reqLength >= rawData.length) { - value = rawData[startIdx] / (1.0f * strokes); - } else { - value = ByteUtil.toInt(rawData[startIdx], rawData[startIdx + 1]) / (1.0f * strokes); - } - - aapsLogger.debug(LTag.PUMPCOMM, "Remaining insulin: " + value); - return value; - } - - - private LocalDateTime decodeTime(byte[] rawContent) { - - int hours = ByteUtil.asUINT8(rawContent[0]); - int minutes = ByteUtil.asUINT8(rawContent[1]); - int seconds = ByteUtil.asUINT8(rawContent[2]); - int year = (ByteUtil.asUINT8(rawContent[4]) & 0x3f) + 1984; - int month = ByteUtil.asUINT8(rawContent[5]); - int day = ByteUtil.asUINT8(rawContent[6]); - try { - LocalDateTime pumpTime = new LocalDateTime(year, month, day, hours, minutes, seconds); - return pumpTime; - } catch (IllegalFieldValueException e) { - aapsLogger.error(LTag.PUMPCOMM, - String.format(Locale.ENGLISH, "decodeTime: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d", - year, month, day, hours, minutes, seconds)); - return null; - } - - } - - - private Map decodeSettingsLoop(byte[] rd) { - - Map map = new HashMap<>(); - - addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map); - addSettingToMap( - "PCFG_MAX_BASAL", - "" - + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()], - rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map); - addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h", - PumpConfigurationGroup.General, map); - - addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map); - - if (rd[10] == 1) { - String patt; - switch (rd[11]) { - case 0: - patt = "STD"; - break; - - case 1: - patt = "A"; - break; - - case 2: - patt = "B"; - break; - - default: - patt = "???"; - break; - } - - addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map); - - } else { - addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", "STD", PumpConfigurationGroup.Basal, map); - } - - addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map); - - return map; - } - - - private Map decodeSettings512(byte[] rd) { - - Map map = new HashMap<>(); - - addSettingToMap("PCFG_AUTOOFF_TIMEOUT", "" + rd[0], PumpConfigurationGroup.General, map); - - if (rd[1] == 4) { - addSettingToMap("PCFG_ALARM_MODE", "Silent", PumpConfigurationGroup.Sound, map); - } else { - addSettingToMap("PCFG_ALARM_MODE", "Normal", PumpConfigurationGroup.Sound, map); - addSettingToMap("PCFG_ALARM_BEEP_VOLUME", "" + rd[1], PumpConfigurationGroup.Sound, map); - } - - addSettingToMap("PCFG_AUDIO_BOLUS_ENABLED", parseResultEnable(rd[2]), PumpConfigurationGroup.Bolus, map); - - if (rd[2] == 1) { - addSettingToMap("PCFG_AUDIO_BOLUS_STEP_SIZE", "" + decodeBolusInsulin(ByteUtil.asUINT8(rd[3])), - PumpConfigurationGroup.Bolus, map); - } - - addSettingToMap("PCFG_VARIABLE_BOLUS_ENABLED", parseResultEnable(rd[4]), PumpConfigurationGroup.Bolus, map); - addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map); - addSettingToMap( - "PCFG_MAX_BASAL", - "" - + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()], - rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map); - addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h", - PumpConfigurationGroup.General, map); - - if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) { - addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] == 0 ? 50 : 100), PumpConfigurationGroup.Insulin, - map); -// LOG.debug("Insulin concentration: " + rd[9]); - } else { - addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] != 0 ? 50 : 100), PumpConfigurationGroup.Insulin, - map); -// LOG.debug("Insulin concentration: " + rd[9]); - } - addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map); - - if (rd[10] == 1) { - String patt; - switch (rd[11]) { - case 0: - patt = "STD"; - break; - - case 1: - patt = "A"; - break; - - case 2: - patt = "B"; - break; - - default: - patt = "???"; - break; - } - - addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map); - - } - - addSettingToMap("CFG_MM_RF_ENABLED", parseResultEnable(rd[12]), PumpConfigurationGroup.General, map); - addSettingToMap("CFG_MM_BLOCK_ENABLED", parseResultEnable(rd[13]), PumpConfigurationGroup.General, map); - - addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map); - - if (rd[14] == 1) { - addSettingToMap("PCFG_TEMP_BASAL_PERCENT", "" + rd[15], PumpConfigurationGroup.Basal, map); - } - - addSettingToMap("CFG_PARADIGM_LINK_ENABLE", parseResultEnable(rd[16]), PumpConfigurationGroup.General, map); - - decodeInsulinActionSetting(rd, map); - - return map; - } - - - private void addSettingToMap(String key, String value, PumpConfigurationGroup group, Map map) { - map.put(key, new PumpSettingDTO(key, value, group)); - } - - - public Map decodeSettings(byte[] rd) { - Map map = decodeSettings512(rd); - - addSettingToMap("PCFG_MM_RESERVOIR_WARNING_TYPE_TIME", rd[18] != 0 ? "PCFG_MM_RESERVOIR_WARNING_TYPE_TIME" - : "PCFG_MM_RESERVOIR_WARNING_TYPE_UNITS", PumpConfigurationGroup.Other, map); - - addSettingToMap("PCFG_MM_SRESERVOIR_WARNING_POINT", "" + ByteUtil.asUINT8(rd[19]), - PumpConfigurationGroup.Other, map); - - addSettingToMap("CFG_MM_KEYPAD_LOCKED", parseResultEnable(rd[20]), PumpConfigurationGroup.Other, map); - - if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)) { - - addSettingToMap("PCFG_BOLUS_SCROLL_STEP_SIZE", "" + rd[21], PumpConfigurationGroup.Bolus, map); - addSettingToMap("PCFG_CAPTURE_EVENT_ENABLE", parseResultEnable(rd[22]), PumpConfigurationGroup.Other, map); - addSettingToMap("PCFG_OTHER_DEVICE_ENABLE", parseResultEnable(rd[23]), PumpConfigurationGroup.Other, map); - addSettingToMap("PCFG_OTHER_DEVICE_PAIRED_STATE", parseResultEnable(rd[24]), PumpConfigurationGroup.Other, - map); - } - - return map; - } - - - private String parseResultEnable(int i) { - switch (i) { - case 0: - return "No"; - case 1: - return "Yes"; - default: - return "???"; - } - } - - - private float getStrokesPerUnit(boolean isBasal) { - return isBasal ? 40.0f : 10; // pumpModel.getBolusStrokes(); - } - - - // 512 - private void decodeInsulinActionSetting(byte[] ai, Map map) { - if (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_512_712)) { - addSettingToMap("PCFG_INSULIN_ACTION_TYPE", (ai[17] != 0 ? "Regular" : "Fast"), - PumpConfigurationGroup.Insulin, map); - } else { - int i = ai[17]; - String s; - - if ((i == 0) || (i == 1)) { - s = ai[17] != 0 ? "Regular" : "Fast"; - } else { - if (i == 15) - s = "Unset"; - else - s = "Curve: " + i; - } - - addSettingToMap("PCFG_INSULIN_ACTION_TYPE", s, PumpConfigurationGroup.Insulin, map); - } - } - - - private double decodeBasalInsulin(int i) { - return (double) i / (double) getStrokesPerUnit(true); - } - - - private double decodeBolusInsulin(int i) { - - return (double) i / (double) getStrokesPerUnit(false); - } - - - private int getSettingIndexMaxBasal() { - return is523orHigher() ? 7 : 6; - } - - - private int getSettingIndexTimeDisplayFormat() { - return is523orHigher() ? 9 : 8; - } - - - private double decodeMaxBolus(byte[] ai) { - return is523orHigher() ? decodeBolusInsulin(ByteUtil.toInt(ai[5], ai[6])) : decodeBolusInsulin(ByteUtil - .asUINT8(ai[5])); - } - - - private boolean is523orHigher() { - return (MedtronicDeviceType.isSameDevice(medtronicUtil.getMedtronicPumpModel(), MedtronicDeviceType.Medtronic_523andHigher)); - } -} diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.kt new file mode 100644 index 0000000000..3796661ef5 --- /dev/null +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.kt @@ -0,0 +1,314 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm + +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil +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.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.defs.PumpConfigurationGroup +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil +import org.joda.time.IllegalFieldValueException +import org.joda.time.LocalDateTime +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Created by andy on 5/9/18. + * High level decoder for data returned through MedtroniUIComm + */ +@Singleton +class MedtronicConverter @Inject constructor( + private val aapsLogger: AAPSLogger, + private val medtronicUtil: MedtronicUtil +) { + + fun convertResponse(pumpType: PumpType, commandType: MedtronicCommandType, rawContent: ByteArray?): Any? { + if ((rawContent == null || rawContent.size < 1) && commandType != MedtronicCommandType.PumpModel) { + aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Content is empty or too short, no data to convert (type=%s,isNull=%b,length=%s)", + commandType.name, rawContent == null, rawContent?.size ?: "-")) + return null + } + aapsLogger.debug(LTag.PUMPCOMM, "Raw response before convert: " + ByteUtil.shortHexString(rawContent)) + return when (commandType) { + MedtronicCommandType.PumpModel -> { + decodeModel(rawContent) + } + + MedtronicCommandType.GetRealTimeClock -> { + decodeTime(rawContent) + } + + MedtronicCommandType.GetRemainingInsulin -> { + decodeRemainingInsulin(rawContent) + } + + MedtronicCommandType.GetBatteryStatus -> { + decodeBatteryStatus(rawContent) // 1 + } + + MedtronicCommandType.GetBasalProfileSTD, MedtronicCommandType.GetBasalProfileA, MedtronicCommandType.GetBasalProfileB -> { + decodeBasalProfile(pumpType, rawContent) + } + + MedtronicCommandType.ReadTemporaryBasal -> { + TempBasalPair(aapsLogger, rawContent) // 5 + } + + MedtronicCommandType.Settings_512 -> { + decodeSettingsLoop(rawContent) + } + + MedtronicCommandType.Settings -> { + decodeSettingsLoop(rawContent) + } + + MedtronicCommandType.SetBolus -> { + rawContent // 1 + } + + else -> { + throw RuntimeException("Unsupported command Type: $commandType") + } + } + } + + private fun decodeBasalProfile(pumpType: PumpType, rawContent: ByteArray?): BasalProfile? { + val basalProfile = BasalProfile(aapsLogger, rawContent!!) + return if (basalProfile.verify(pumpType)) basalProfile else null + } + + private fun decodeModel(rawContent: ByteArray?): MedtronicDeviceType { + if (rawContent == null || rawContent.size < 4) { + aapsLogger.warn(LTag.PUMPCOMM, "Error reading PumpModel, returning Unknown_Device") + return MedtronicDeviceType.Unknown_Device + } + val rawModel = StringUtil.fromBytes(ByteUtil.substring(rawContent, 1, 3)) + val pumpModel = MedtronicDeviceType.getByDescription(rawModel) + aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "PumpModel: [raw=%s, resolved=%s]", rawModel, pumpModel.name)) + if (pumpModel != MedtronicDeviceType.Unknown_Device) { + if (!medtronicUtil.isModelSet) { + medtronicUtil.medtronicPumpModel = pumpModel + } + } + return pumpModel + } + + private fun decodeBatteryStatus(rawData: ByteArray?): BatteryStatusDTO { + // 00 7C 00 00 + val batteryStatus = BatteryStatusDTO() + val status = rawData!![0].toInt() + if (status == 0) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Normal + } else if (status == 1) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Low + } else if (status == 2) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Unknown + } + if (rawData.size > 1) { + var d: Double? = null + + // if response in 3 bytes then we add additional information + d = if (rawData.size == 2) { + rawData[1] * 1.0 / 100.0 + } else { + ByteUtil.toInt(rawData[1], rawData[2]) * 1.0 / 100.0 + } + batteryStatus.voltage = d + batteryStatus.extendedDataReceived = true + } + return batteryStatus + } + + private fun decodeRemainingInsulin(rawData: ByteArray?): Float { + var startIdx = 0 + val pumpModel = medtronicUtil.medtronicPumpModel + val strokes = pumpModel?.bolusStrokes ?: 10 + if (strokes == 40) { + startIdx = 2 + } + val reqLength = startIdx + 1 + var value = 0f + value = if (reqLength >= rawData!!.size) { + rawData[startIdx] / (1.0f * strokes) + } else { + ByteUtil.toInt(rawData[startIdx], rawData[startIdx + 1]) / (1.0f * strokes) + } + aapsLogger.debug(LTag.PUMPCOMM, "Remaining insulin: $value") + return value + } + + private fun decodeTime(rawContent: ByteArray?): LocalDateTime? { + val hours = ByteUtil.asUINT8(rawContent!![0]) + val minutes = ByteUtil.asUINT8(rawContent[1]) + val seconds = ByteUtil.asUINT8(rawContent[2]) + val year = (ByteUtil.asUINT8(rawContent[4]) and 0x3f) + 1984 + val month = ByteUtil.asUINT8(rawContent[5]) + val day = ByteUtil.asUINT8(rawContent[6]) + return try { + LocalDateTime(year, month, day, hours, minutes, seconds) + } catch (e: IllegalFieldValueException) { + aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "decodeTime: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d", + year, month, day, hours, minutes, seconds)) + null + } + } + + private fun decodeSettingsLoop(rd: ByteArray?): Map { + val map: MutableMap = HashMap() + addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map) + addSettingToMap( + "PCFG_MAX_BASAL", "" + + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd!![settingIndexMaxBasal].toInt(), + rd[settingIndexMaxBasal + 1].toInt())), PumpConfigurationGroup.Basal, map) + addSettingToMap("CFG_BASE_CLOCK_MODE", if (rd[settingIndexTimeDisplayFormat].toInt() == 0) "12h" else "24h", + PumpConfigurationGroup.General, map) + addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10].toInt()), PumpConfigurationGroup.Basal, map) + if (rd[10].toInt() == 1) { + val patt: String + patt = when (rd[11].toInt()) { + 0 -> "STD" + 1 -> "A" + 2 -> "B" + else -> "???" + } + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map) + } else { + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", "STD", PumpConfigurationGroup.Basal, map) + } + addSettingToMap("PCFG_TEMP_BASAL_TYPE", if (rd[14].toInt() != 0) "Percent" else "Units", PumpConfigurationGroup.Basal, map) + return map + } + + private fun decodeSettings512(rd: ByteArray): MutableMap { + val map: MutableMap = HashMap() + addSettingToMap("PCFG_AUTOOFF_TIMEOUT", "" + rd[0], PumpConfigurationGroup.General, map) + if (rd[1].toInt() == 4) { + addSettingToMap("PCFG_ALARM_MODE", "Silent", PumpConfigurationGroup.Sound, map) + } else { + addSettingToMap("PCFG_ALARM_MODE", "Normal", PumpConfigurationGroup.Sound, map) + addSettingToMap("PCFG_ALARM_BEEP_VOLUME", "" + rd[1], PumpConfigurationGroup.Sound, map) + } + addSettingToMap("PCFG_AUDIO_BOLUS_ENABLED", parseResultEnable(rd[2].toInt()), PumpConfigurationGroup.Bolus, map) + if (rd[2].toInt() == 1) { + addSettingToMap("PCFG_AUDIO_BOLUS_STEP_SIZE", "" + decodeBolusInsulin(ByteUtil.asUINT8(rd[3])), + PumpConfigurationGroup.Bolus, map) + } + addSettingToMap("PCFG_VARIABLE_BOLUS_ENABLED", parseResultEnable(rd[4].toInt()), PumpConfigurationGroup.Bolus, map) + addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map) + addSettingToMap( + "PCFG_MAX_BASAL", "" + + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[settingIndexMaxBasal].toInt(), + rd[settingIndexMaxBasal + 1].toInt())), PumpConfigurationGroup.Basal, map) + addSettingToMap("CFG_BASE_CLOCK_MODE", if (rd[settingIndexTimeDisplayFormat].toInt() == 0) "12h" else "24h", + PumpConfigurationGroup.General, map) + if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) { + addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + if (rd[9].toInt() == 0) 50 else 100, PumpConfigurationGroup.Insulin, + map) + // LOG.debug("Insulin concentration: " + rd[9]); + } else { + addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + if (rd[9].toInt() != 0) 50 else 100, PumpConfigurationGroup.Insulin, + map) + // LOG.debug("Insulin concentration: " + rd[9]); + } + addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10].toInt()), PumpConfigurationGroup.Basal, map) + if (rd[10].toInt() == 1) { + val patt: String + patt = when (rd[11].toInt()) { + 0 -> "STD" + 1 -> "A" + 2 -> "B" + else -> "???" + } + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map) + } + addSettingToMap("CFG_MM_RF_ENABLED", parseResultEnable(rd[12].toInt()), PumpConfigurationGroup.General, map) + addSettingToMap("CFG_MM_BLOCK_ENABLED", parseResultEnable(rd[13].toInt()), PumpConfigurationGroup.General, map) + addSettingToMap("PCFG_TEMP_BASAL_TYPE", if (rd[14].toInt() != 0) "Percent" else "Units", PumpConfigurationGroup.Basal, map) + if (rd[14].toInt() == 1) { + addSettingToMap("PCFG_TEMP_BASAL_PERCENT", "" + rd[15], PumpConfigurationGroup.Basal, map) + } + addSettingToMap("CFG_PARADIGM_LINK_ENABLE", parseResultEnable(rd[16].toInt()), PumpConfigurationGroup.General, map) + decodeInsulinActionSetting(rd, map) + return map + } + + private fun addSettingToMap(key: String, value: String, group: PumpConfigurationGroup, map: MutableMap) { + map[key] = PumpSettingDTO(key, value, group) + } + + fun decodeSettings(rd: ByteArray): Map { + val map = decodeSettings512(rd) + addSettingToMap("PCFG_MM_RESERVOIR_WARNING_TYPE_TIME", if (rd[18].toInt() != 0) "PCFG_MM_RESERVOIR_WARNING_TYPE_TIME" else "PCFG_MM_RESERVOIR_WARNING_TYPE_UNITS", PumpConfigurationGroup.Other, map) + addSettingToMap("PCFG_MM_SRESERVOIR_WARNING_POINT", "" + ByteUtil.asUINT8(rd[19]), + PumpConfigurationGroup.Other, map) + addSettingToMap("CFG_MM_KEYPAD_LOCKED", parseResultEnable(rd[20].toInt()), PumpConfigurationGroup.Other, map) + if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher)) { + addSettingToMap("PCFG_BOLUS_SCROLL_STEP_SIZE", "" + rd[21], PumpConfigurationGroup.Bolus, map) + addSettingToMap("PCFG_CAPTURE_EVENT_ENABLE", parseResultEnable(rd[22].toInt()), PumpConfigurationGroup.Other, map) + addSettingToMap("PCFG_OTHER_DEVICE_ENABLE", parseResultEnable(rd[23].toInt()), PumpConfigurationGroup.Other, map) + addSettingToMap("PCFG_OTHER_DEVICE_PAIRED_STATE", parseResultEnable(rd[24].toInt()), PumpConfigurationGroup.Other, + map) + } + return map + } + + private fun parseResultEnable(i: Int): String { + return when (i) { + 0 -> "No" + 1 -> "Yes" + else -> "???" + } + } + + private fun getStrokesPerUnit(isBasal: Boolean): Float { + return if (isBasal) 40.0f else 10.0f // pumpModel.getBolusStrokes(); + } + + // 512 + private fun decodeInsulinActionSetting(ai: ByteArray, map: MutableMap) { + if (MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_512_712)) { + addSettingToMap("PCFG_INSULIN_ACTION_TYPE", if (ai[17].toInt() != 0) "Regular" else "Fast", + PumpConfigurationGroup.Insulin, map) + } else { + val i = ai[17].toInt() + val s: String + s = if (i == 0 || i == 1) { + if (ai[17].toInt() != 0) "Regular" else "Fast" + } else { + if (i == 15) "Unset" else "Curve: $i" + } + addSettingToMap("PCFG_INSULIN_ACTION_TYPE", s, PumpConfigurationGroup.Insulin, map) + } + } + + private fun decodeBasalInsulin(i: Int): Double { + return i.toDouble() / getStrokesPerUnit(true).toDouble() + } + + private fun decodeBolusInsulin(i: Int): Double { + return i.toDouble() / getStrokesPerUnit(false).toDouble() + } + + private val settingIndexMaxBasal: Int + private get() = if (is523orHigher()) 7 else 6 + + private val settingIndexTimeDisplayFormat: Int + private get() = if (is523orHigher()) 9 else 8 + + private fun decodeMaxBolus(ai: ByteArray?): Double { + return if (is523orHigher()) decodeBolusInsulin(ByteUtil.toInt(ai!![5], ai[6])) else decodeBolusInsulin(ByteUtil + .asUINT8(ai!![5])) + } + + private fun is523orHigher(): Boolean { + return MedtronicDeviceType.isSameDevice(medtronicUtil.medtronicPumpModel, MedtronicDeviceType.Medtronic_523andHigher) + } + +} \ No newline at end of file diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.kt index d56798dd3a..77f1ee32c1 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.kt +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.kt @@ -10,7 +10,9 @@ import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms.CGMSH import okhttp3.internal.and import org.joda.time.LocalDateTime import org.slf4j.Logger +import org.slf4j.LoggerFactory import java.util.* +import org.slf4j.LoggerFactory.getLogger as getLogger1 /** * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes @@ -264,6 +266,6 @@ class MedtronicCGMSHistoryDecoder : MedtronicHistoryDecoder() } companion object { - private val LOG: Logger = getLogger(LTag.PUMPCOMM) + private val LOG: Logger = getLogger1("MedtronicCGMSHistoryDecoder") } } \ No newline at end of file diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java deleted file mode 100644 index 3cbd03f20d..0000000000 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java +++ /dev/null @@ -1,384 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; - -import androidx.annotation.NonNull; - -import com.google.gson.annotations.Expose; - -import org.joda.time.Instant; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; -import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; -import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; - -/** - * Created by geoff on 6/1/15. - *

- * There are three basal profiles stored on the pump. (722 only?) They are all parsed the same, the user just has 3 to - * choose from: Standard, A, and B - *

- * The byte array is 48 times three byte entries long, plus a zero? If the profile is completely empty, it should have - * one entry: [0,0,0x3F]. The first entry of [0,0,0] marks the end of the used entries. - *

- * Each entry is assumed to span from the specified start time to the start time of the next entry, or to midnight if - * there are no more entries. - *

- * Individual entries are of the form [r,z,m] where r is the rate (in 0.025 U increments) z is zero (?) m is the start - * time-of-day for the basal rate period (in 30 minute increments) - */ -public class BasalProfile { - - private final AAPSLogger aapsLogger; - - public static final int MAX_RAW_DATA_SIZE = (48 * 3) + 1; - private static final boolean DEBUG_BASALPROFILE = false; - @Expose - private byte[] mRawData; // store as byte array to make transport (via parcel) easier - private List listEntries; - - - public BasalProfile(AAPSLogger aapsLogger) { - this.aapsLogger = aapsLogger; - init(); - } - - - public BasalProfile(AAPSLogger aapsLogger, byte[] data) { - this.aapsLogger = aapsLogger; - setRawData(data); - } - - - // this asUINT8 should be combined with Record.asUINT8, and placed in a new util class. - private static int readUnsignedByte(byte b) { - return (b < 0) ? b + 256 : b; - } - - - public void init() { - mRawData = new byte[MAX_RAW_DATA_SIZE]; - mRawData[0] = 0; - mRawData[1] = 0; - mRawData[2] = 0x3f; - } - - - private boolean setRawData(byte[] data) { - if (data == null) { - aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!"); - return false; - } - - // if we have just one entry through all day it looks like just length 1 - if (data.length == 1) { - data = MedtronicUtil.createByteArray(data[0], (byte) 0, (byte) 0); - } - - if (data.length == MAX_RAW_DATA_SIZE) { - mRawData = data; - } else { - int len = Math.min(MAX_RAW_DATA_SIZE, data.length); - mRawData = new byte[MAX_RAW_DATA_SIZE]; - System.arraycopy(data, 0, mRawData, 0, len); - } - - return true; - } - - - public boolean setRawDataFromHistory(byte[] data) { - if (data == null) { - aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!"); - return false; - } - - mRawData = new byte[MAX_RAW_DATA_SIZE]; - int item = 0; - - for (int i = 0; i < data.length - 2; i += 3) { - - if ((data[i] == 0) && (data[i + 1] == 0) && (data[i + 2] == 0)) { - mRawData[i] = 0; - mRawData[i + 1] = 0; - mRawData[i + 2] = 0; - } - - mRawData[i] = data[i + 1]; - mRawData[i + 1] = data[i + 2]; - mRawData[i + 2] = data[i]; - } - - return true; - } - - - public void dumpBasalProfile() { - aapsLogger.debug(LTag.PUMPCOMM, "Basal Profile entries:"); - List entries = getEntries(); - for (int i = 0; i < entries.size(); i++) { - BasalProfileEntry entry = entries.get(i); - String startString = entry.startTime.toString("HH:mm"); - // this doesn't work - aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Entry %d, rate=%.3f (%s), start=%s (0x%02X)", i + 1, entry.rate, - ByteUtil.getHex(entry.rate_raw), startString, entry.startTime_raw)); - - } - } - - - public String getBasalProfileAsString() { - StringBuffer sb = new StringBuffer("Basal Profile entries:\n"); - List entries = getEntries(); - for (int i = 0; i < entries.size(); i++) { - BasalProfileEntry entry = entries.get(i); - String startString = entry.startTime.toString("HH:mm"); - - sb.append(String.format(Locale.ENGLISH, "Entry %d, rate=%.3f, start=%s\n", i + 1, entry.rate, startString)); - } - - return sb.toString(); - } - - public String basalProfileToStringError() { - return "Basal Profile [rawData=" + ByteUtil.shortHexString(this.getRawData()) + "]"; - } - - - public String basalProfileToString() { - StringBuffer sb = new StringBuffer("Basal Profile ["); - List entries = getEntries(); - for (int i = 0; i < entries.size(); i++) { - BasalProfileEntry entry = entries.get(i); - String startString = entry.startTime.toString("HH:mm"); - - sb.append(String.format(Locale.ENGLISH, "%s=%.3f, ", startString, entry.rate)); - } - - sb.append("]"); - - return sb.toString(); - } - - - // TODO: this function must be expanded to include changes in which profile is in use. - // and changes to the profiles themselves. - public BasalProfileEntry getEntryForTime(Instant when) { - BasalProfileEntry rval = new BasalProfileEntry(); - List 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"))); - return rval; - } - // Log.w(TAG,"Assuming first entry"); - rval = entries.get(0); - if (entries.size() == 1) { - aapsLogger.debug(LTag.PUMPCOMM, "getEntryForTime: Only one entry in profile"); - return rval; - } - - int localMillis = when.toDateTime().toLocalTime().getMillisOfDay(); - boolean done = false; - int i = 1; - while (!done) { - BasalProfileEntry entry = entries.get(i); - if (DEBUG_BASALPROFILE) { - aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Comparing 'now'=%s to entry 'start time'=%s", when.toDateTime().toLocalTime() - .toString("HH:mm"), entry.startTime.toString("HH:mm"))); - } - if (localMillis >= entry.startTime.getMillisOfDay()) { - rval = entry; - if (DEBUG_BASALPROFILE) - aapsLogger.debug(LTag.PUMPCOMM, "Accepted Entry"); - } else { - // entry at i has later start time, keep older entry - if (DEBUG_BASALPROFILE) - aapsLogger.debug(LTag.PUMPCOMM, "Rejected Entry"); - done = true; - } - i++; - if (i >= entries.size()) { - done = true; - } - } - if (DEBUG_BASALPROFILE) { - aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): Returning entry: rate=%.3f (%s), start=%s (%d)", when - .toDateTime().toLocalTime().toString("HH:mm"), rval.rate, ByteUtil.getHex(rval.rate_raw), - rval.startTime.toString("HH:mm"), rval.startTime_raw)); - } - return rval; - } - - - public List getEntries() { - List entries = new ArrayList<>(); - - if (mRawData == null || mRawData[2] == 0x3f) { - aapsLogger.warn(LTag.PUMPCOMM, "Raw Data is empty."); - return entries; // an empty list - } - int r, st; - - for (int i = 0; i < mRawData.length - 2; i += 3) { - - if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0)) - break; - - if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0x3f)) - break; - - r = MedtronicUtil.makeUnsignedShort(mRawData[i + 1], mRawData[i]); // readUnsignedByte(mRawData[i]); - st = readUnsignedByte(mRawData[i + 2]); - - try { - entries.add(new BasalProfileEntry(aapsLogger, r, st)); - } catch (Exception ex) { - aapsLogger.error(LTag.PUMPCOMM, "Error decoding basal profile from bytes: " + ByteUtil.shortHexString(mRawData)); - throw ex; - } - - } - - return entries; - } - - - /** - * This is used to prepare new profile - * - * @param entry - */ - public void addEntry(BasalProfileEntry entry) { - if (listEntries == null) - listEntries = new ArrayList<>(); - - listEntries.add(entry); - } - - - public void generateRawDataFromEntries() { - - List outData = new ArrayList<>(); - - for (BasalProfileEntry profileEntry : listEntries) { - - byte[] strokes = MedtronicUtil.getBasalStrokes(profileEntry.rate, true); - - outData.add(profileEntry.rate_raw[0]); - outData.add(profileEntry.rate_raw[1]); - outData.add(profileEntry.startTime_raw); - } - - this.setRawData(MedtronicUtil.createByteArray(outData)); - - // return this.mRawData; - } - - - public Double[] getProfilesByHour(PumpType pumpType) { - - List entries = null; - - try { - entries = getEntries(); - } catch (Exception ex) { - aapsLogger.error(LTag.PUMPCOMM, "============================================================================="); - aapsLogger.error(LTag.PUMPCOMM, " Error generating entries. Ex.: " + ex, ex); - aapsLogger.error(LTag.PUMPCOMM, " rawBasalValues: " + ByteUtil.shortHexString(this.getRawData())); - aapsLogger.error(LTag.PUMPCOMM, "============================================================================="); - - //FabricUtil.createEvent("MedtronicBasalProfileGetByHourError", null); - } - - if (entries == null || entries.size() == 0) { - Double[] basalByHour = new Double[24]; - - for (int i = 0; i < 24; i++) { - basalByHour[i] = 0.0d; - } - - return basalByHour; - } - - Double[] basalByHour = new Double[24]; - - for (int i = 0; i < entries.size(); i++) { - BasalProfileEntry current = entries.get(i); - - int currentTime = (current.startTime_raw % 2 == 0) ? current.startTime_raw : current.startTime_raw - 1; - - currentTime = (currentTime * 30) / 60; - - int lastHour; - if ((i + 1) == entries.size()) { - lastHour = 24; - } else { - BasalProfileEntry basalProfileEntry = entries.get(i + 1); - - int rawTime = (basalProfileEntry.startTime_raw % 2 == 0) ? basalProfileEntry.startTime_raw - : basalProfileEntry.startTime_raw - 1; - - lastHour = (rawTime * 30) / 60; - } - - // System.out.println("Current time: " + currentTime + " Next Time: " + lastHour); - - for (int j = currentTime; j < lastHour; j++) { - if (pumpType == null) - basalByHour[j] = current.rate; - else - basalByHour[j] = pumpType.determineCorrectBasalSize(current.rate); - } - } - - return basalByHour; - } - - - public static String getProfilesByHourToString(Double[] data) { - - StringBuilder stringBuilder = new StringBuilder(); - - for (Double value : data) { - stringBuilder.append(String.format("%.3f", value)); - stringBuilder.append(" "); - } - - return stringBuilder.toString(); - - } - - - public byte[] getRawData() { - return this.mRawData; - } - - - @NonNull public String toString() { - return basalProfileToString(); - } - - - public boolean verify(PumpType pumpType) { - - try { - getEntries(); - } catch (Exception ex) { - return false; - } - - Double[] profilesByHour = getProfilesByHour(pumpType); - - for (Double aDouble : profilesByHour) { - if (aDouble > 35.0d) - return false; - } - - return true; - } -} diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.kt new file mode 100644 index 0000000000..3777c30559 --- /dev/null +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.kt @@ -0,0 +1,315 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto + +import com.google.gson.annotations.Expose +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil +import org.joda.time.Instant +import java.util.* + +/** + * Created by geoff on 6/1/15. + * + * + * There are three basal profiles stored on the pump. (722 only?) They are all parsed the same, the user just has 3 to + * choose from: Standard, A, and B + * + * + * The byte array is 48 times three byte entries long, plus a zero? If the profile is completely empty, it should have + * one entry: [0,0,0x3F]. The first entry of [0,0,0] marks the end of the used entries. + * + * + * Each entry is assumed to span from the specified start time to the start time of the next entry, or to midnight if + * there are no more entries. + * + * + * Individual entries are of the form [r,z,m] where r is the rate (in 0.025 U increments) z is zero (?) m is the start + * time-of-day for the basal rate period (in 30 minute increments) + */ +class BasalProfile { + + private val aapsLogger: AAPSLogger + + @Expose var rawData : ByteArray? = null // store as byte array to make transport (via parcel) easier + private set + + private var listEntries: MutableList? = null + + constructor(aapsLogger: AAPSLogger) { + this.aapsLogger = aapsLogger + init() + } + + constructor(aapsLogger: AAPSLogger, data: ByteArray) { + this.aapsLogger = aapsLogger + setRawData(data) + } + + fun init() { + rawData = ByteArray(MAX_RAW_DATA_SIZE) + rawData!![0] = 0 + rawData!![1] = 0 + rawData!![2] = 0x3f + } + + private fun setRawData(data: ByteArray): Boolean { + var data: ByteArray? = data + if (data == null) { + aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!") + return false + } + + // if we have just one entry through all day it looks like just length 1 + if (data.size == 1) { + data = MedtronicUtil.createByteArray(data[0], 0.toByte(), 0.toByte()) + } + if (data!!.size == MAX_RAW_DATA_SIZE) { + rawData = data + } else { + val len = Math.min(MAX_RAW_DATA_SIZE, data.size) + rawData = ByteArray(MAX_RAW_DATA_SIZE) + System.arraycopy(data, 0, rawData, 0, len) + } + return true + } + + fun setRawDataFromHistory(data: ByteArray?): Boolean { + if (data == null) { + aapsLogger.error(LTag.PUMPCOMM, "setRawData: buffer is null!") + return false + } + rawData = ByteArray(MAX_RAW_DATA_SIZE) + val item = 0 + var i = 0 + while (i < data.size - 2) { + if (data[i] == 0.toByte() && data[i + 1] == 0.toByte() && data[i + 2] == 0.toByte()) { + rawData!![i] = 0 + rawData!![i + 1] = 0 + rawData!![i + 2] = 0 + } + rawData!![i] = data[i + 1] + rawData!![i + 1] = data[i + 2] + rawData!![i + 2] = data[i] + i += 3 + } + return true + } + + fun dumpBasalProfile() { + aapsLogger.debug(LTag.PUMPCOMM, "Basal Profile entries:") + val entries = entries + for (i in entries.indices) { + val entry = entries[i] + val startString = entry.startTime!!.toString("HH:mm") + // this doesn't work + aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Entry %d, rate=%.3f (%s), start=%s (0x%02X)", i + 1, entry.rate, + ByteUtil.getHex(entry.rate_raw), startString, entry.startTime_raw)) + } + } + + val basalProfileAsString: String + get() { + val sb = StringBuffer("Basal Profile entries:\n") + val entries = entries + for (i in entries.indices) { + val entry = entries[i] + val startString = entry.startTime!!.toString("HH:mm") + sb.append(String.format(Locale.ENGLISH, "Entry %d, rate=%.3f, start=%s\n", i + 1, entry.rate, startString)) + } + return sb.toString() + } + + fun basalProfileToStringError(): String { + return "Basal Profile [rawData=" + ByteUtil.shortHexString(rawData) + "]" + } + + fun basalProfileToString(): String { + val sb = StringBuffer("Basal Profile [") + val entries = entries + for (i in entries.indices) { + val entry = entries[i] + val startString = entry.startTime!!.toString("HH:mm") + sb.append(String.format(Locale.ENGLISH, "%s=%.3f, ", startString, entry.rate)) + } + sb.append("]") + return sb.toString() + } + + // TODO: this function must be expanded to include changes in which profile is in use. + // and changes to the profiles themselves. + fun getEntryForTime(`when`: Instant): BasalProfileEntry { + var rval = BasalProfileEntry() + val entries = entries + if (entries.size == 0) { + aapsLogger.warn(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): table is empty", + `when`.toDateTime().toLocalTime().toString("HH:mm"))) + return rval + } + // Log.w(TAG,"Assuming first entry"); + rval = entries[0] + if (entries.size == 1) { + aapsLogger.debug(LTag.PUMPCOMM, "getEntryForTime: Only one entry in profile") + return rval + } + val localMillis = `when`.toDateTime().toLocalTime().millisOfDay + var done = false + var i = 1 + while (!done) { + val entry = entries[i] + if (DEBUG_BASALPROFILE) { + aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Comparing 'now'=%s to entry 'start time'=%s", `when`.toDateTime().toLocalTime() + .toString("HH:mm"), entry.startTime!!.toString("HH:mm"))) + } + if (localMillis >= entry.startTime!!.millisOfDay) { + rval = entry + if (DEBUG_BASALPROFILE) aapsLogger.debug(LTag.PUMPCOMM, "Accepted Entry") + } else { + // entry at i has later start time, keep older entry + if (DEBUG_BASALPROFILE) aapsLogger.debug(LTag.PUMPCOMM, "Rejected Entry") + done = true + } + i++ + if (i >= entries.size) { + done = true + } + } + if (DEBUG_BASALPROFILE) { + aapsLogger.debug(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "getEntryForTime(%s): Returning entry: rate=%.3f (%s), start=%s (%d)", `when` + .toDateTime().toLocalTime().toString("HH:mm"), rval.rate, ByteUtil.getHex(rval.rate_raw), + rval.startTime!!.toString("HH:mm"), rval.startTime_raw)) + } + return rval + }// readUnsignedByte(mRawData[i]); + + // an empty list + val entries: List + get() { + val entries: MutableList = 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 + } + + /** + * This is used to prepare new profile + * + * @param entry + */ + fun addEntry(entry: BasalProfileEntry) { + if (listEntries == null) listEntries = ArrayList() + listEntries!!.add(entry) + } + + fun generateRawDataFromEntries() { + val outData: MutableList = ArrayList() + for (profileEntry in listEntries!!) { + val strokes = MedtronicUtil.getBasalStrokes(profileEntry.rate, true) + outData.add(profileEntry.rate_raw[0]) + outData.add(profileEntry.rate_raw[1]) + outData.add(profileEntry.startTime_raw) + } + setRawData(MedtronicUtil.createByteArray(outData)) + + // return this.mRawData; + } + + fun getProfilesByHour(pumpType: PumpType?): Array { + var entries: List? = null + try { + entries = entries + } catch (ex: Exception) { + aapsLogger.error(LTag.PUMPCOMM, "=============================================================================") + aapsLogger.error(LTag.PUMPCOMM, " Error generating entries. Ex.: $ex", ex) + aapsLogger.error(LTag.PUMPCOMM, " rawBasalValues: " + ByteUtil.shortHexString(rawData)) + aapsLogger.error(LTag.PUMPCOMM, "=============================================================================") + + //FabricUtil.createEvent("MedtronicBasalProfileGetByHourError", null); + } + if (entries == null || entries.size == 0) { + val basalByHour = arrayOfNulls(24) + for (i in 0..23) { + basalByHour[i] = 0.0 + } + return basalByHour + } + val basalByHour = arrayOfNulls(24) + for (i in entries.indices) { + val current = entries[i] + var currentTime = if (current.startTime_raw % 2 == 0) current.startTime_raw.toInt() else current.startTime_raw - 1 + currentTime = currentTime * 30 / 60 + var lastHour: Int + lastHour = if (i + 1 == entries.size) { + 24 + } else { + val basalProfileEntry = entries[i + 1] + val rawTime = if (basalProfileEntry.startTime_raw % 2 == 0) basalProfileEntry.startTime_raw.toInt() else basalProfileEntry.startTime_raw - 1 + rawTime * 30 / 60 + } + + // System.out.println("Current time: " + currentTime + " Next Time: " + lastHour); + for (j in currentTime until lastHour) { + if (pumpType == null) basalByHour[j] = current.rate else basalByHour[j] = pumpType.determineCorrectBasalSize(current.rate) + } + } + return basalByHour + } + + override fun toString(): String { + return basalProfileToString() + } + + fun verify(pumpType: PumpType?): Boolean { + try { + entries + } catch (ex: Exception) { + return false + } + val profilesByHour = getProfilesByHour(pumpType) + for (aDouble in profilesByHour) { + if (aDouble!! > 35.0) return false + } + return true + } + + + + companion object { + const val MAX_RAW_DATA_SIZE = 48 * 3 + 1 + private const val DEBUG_BASALPROFILE = false + + // this asUINT8 should be combined with Record.asUINT8, and placed in a new util class. + private fun readUnsignedByte(b: Byte): Int { + return if (b < 0) b + 256 else b.toInt() + } + + @JvmStatic + fun getProfilesByHourToString(data: Array): String { + val stringBuilder = StringBuilder() + for (value in data) { + stringBuilder.append(String.format("%.3f", value)) + stringBuilder.append(" ") + } + return stringBuilder.toString() + } + } +} \ No newline at end of file diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java deleted file mode 100644 index da2a7181c0..0000000000 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java +++ /dev/null @@ -1,81 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; - -import org.joda.time.LocalTime; - -import java.util.Locale; - -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; - -/** - * Created by geoff on 6/1/15. - * This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile - * - fixed rate is not one bit but two - */ -public class BasalProfileEntry { - - byte[] rate_raw; - public double rate; - byte startTime_raw; - public LocalTime startTime; // Just a "time of day" - - public BasalProfileEntry() { - rate = -9.999E6; - rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(0xFF, true); - startTime = new LocalTime(0); - startTime_raw = (byte) 0xFF; - } - - public BasalProfileEntry(double rate, int hour, int minutes) { - byte[] data = MedtronicUtil.getBasalStrokes(rate, true); - - rate_raw = new byte[2]; - rate_raw[0] = data[1]; - rate_raw[1] = data[0]; - - int interval = hour * 2; - - if (minutes == 30) { - interval++; - } - - startTime_raw = (byte) interval; - startTime = new LocalTime(hour, minutes == 30 ? 30 : 0); - } - - BasalProfileEntry(AAPSLogger aapsLogger, int rateStrokes, int startTimeInterval) { - // rateByte is insulin delivery rate, U/hr, in 0.025 U increments - // startTimeByte is time-of-day, in 30 minute increments - rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateStrokes, true); - rate = rateStrokes * 0.025; - startTime_raw = (byte) startTimeInterval; - - try { - startTime = new LocalTime(startTimeInterval / 2, (startTimeInterval % 2) * 30); - } catch (Exception ex) { - aapsLogger.error(LTag.PUMPCOMM, - String.format(Locale.ENGLISH, "Error creating BasalProfileEntry: startTimeInterval=%d, startTime_raw=%d, hours=%d, rateStrokes=%d", - startTimeInterval, startTime_raw, startTimeInterval / 2, rateStrokes)); - throw ex; - } - - } - - BasalProfileEntry(byte rateByte, int startTimeByte) { - // rateByte is insulin delivery rate, U/hr, in 0.025 U increments - // startTimeByte is time-of-day, in 30 minute increments - rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateByte, true); - rate = rateByte * 0.025; - startTime_raw = (byte) startTimeByte; - startTime = new LocalTime(startTimeByte / 2, (startTimeByte % 2) * 30); - } - - public void setStartTime(LocalTime localTime) { - this.startTime = localTime; - } - - public void setRate(double rate) { - this.rate = rate; - } -} diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.kt b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.kt new file mode 100644 index 0000000000..6ffbefcf2d --- /dev/null +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.kt @@ -0,0 +1,74 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto + +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil +import org.joda.time.LocalTime +import java.util.* + +/** + * Created by geoff on 6/1/15. + * This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile + * - fixed rate is not one bit but two + */ +class BasalProfileEntry { + + var rate_raw: ByteArray + var rate = 0.0 + set(value) { + field = value + } + + var startTime_raw: Byte + var startTime : LocalTime? = null // Just a "time of day" + set(value) { + field = value + } + + + constructor() { + rate = -9.999E6 + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(0xFF, true) + startTime = LocalTime(0) + startTime_raw = 0xFF.toByte() + } + + constructor(rate: Double, hour: Int, minutes: Int) { + val data = MedtronicUtil.getBasalStrokes(rate, true) + rate_raw = ByteArray(2) + rate_raw[0] = data[1] + rate_raw[1] = data[0] + var interval = hour * 2 + if (minutes == 30) { + interval++ + } + startTime_raw = interval.toByte() + startTime = LocalTime(hour, if (minutes == 30) 30 else 0) + } + + internal constructor(aapsLogger: AAPSLogger, rateStrokes: Int, startTimeInterval: Int) { + // rateByte is insulin delivery rate, U/hr, in 0.025 U increments + // startTimeByte is time-of-day, in 30 minute increments + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateStrokes, true) + rate = rateStrokes * 0.025 + startTime_raw = startTimeInterval.toByte() + startTime = try { + LocalTime(startTimeInterval / 2, startTimeInterval % 2 * 30) + } catch (ex: Exception) { + aapsLogger.error(LTag.PUMPCOMM, String.format(Locale.ENGLISH, "Error creating BasalProfileEntry: startTimeInterval=%d, startTime_raw=%d, hours=%d, rateStrokes=%d", + startTimeInterval, startTime_raw, startTimeInterval / 2, rateStrokes)) + throw ex + } + } + + internal constructor(rateByte: Byte, startTimeByte: Int) { + // rateByte is insulin delivery rate, U/hr, in 0.025 U increments + // startTimeByte is time-of-day, in 30 minute increments + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateByte.toInt(), true) + rate = rateByte * 0.025 + startTime_raw = startTimeByte.toByte() + startTime = LocalTime(startTimeByte / 2, startTimeByte % 2 * 30) + } + + +} \ No newline at end of file diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java index 0c14ac42f8..dfa4e83867 100755 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java @@ -215,32 +215,6 @@ public enum MedtronicCommandType implements Serializable // , MinimedCommandType } - // MedtronicCommandType(int code, String description, MedtronicDeviceType devices, -// MinimedCommandParameterType parameterType) { -// this(code, description, devices, parameterType, 64, 1, 0, 0, 0, 0); -// } -// -// -// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, -// MinimedCommandParameterType parameterType, int expectedLength) { -// this(code, description, devices, parameterType, 64, 1, 0, 0, 0, expectedLength); -// } -// -// -// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, -// MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType) { -// this(code, description, devices, parameterType, recordLength, maxRecords, 0, 0, commandType, 0); -// } -// -// -// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, -// MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType, -// int expectedLength) { -// this(code, description, devices, parameterType, recordLength, maxRecords, 0, 0, commandType, -// expectedLength); -// } -// -// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, MinimedCommandParameterType parameterType, byte[] cmd_params) { this(code, description, devices, parameterType, 0, 1, 0, 0, 11, 0); @@ -256,14 +230,6 @@ public enum MedtronicCommandType implements Serializable // , MinimedCommandType this(code, description, devices, parameterType, 64, 1, 0, null); } - - // NEW - MedtronicCommandType(int code, String description, MedtronicDeviceType devices, - MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType) { - this(code, description, devices, parameterType, recordLength, maxRecords, 0, null); - } - - // NEW MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // MinimedCommandParameterType parameterType, int expectedLength) { @@ -390,22 +356,6 @@ public enum MedtronicCommandType implements Serializable // , MinimedCommandType } - public boolean canReturnData() { - System.out.println("CanReturnData: ]id=" + this.name() + "max=" + this.maxRecords + "recLen=" + recordLength); - return (this.maxRecords * this.recordLength) > 0; - } - - - public int getRecordLength() { - return recordLength; - } - - - public int getMaxRecords() { - return maxRecords; - } - - public byte getCommandCode() { return commandCode; } @@ -420,16 +370,6 @@ public enum MedtronicCommandType implements Serializable // , MinimedCommandType } - public byte[] getCommandParameters() { - return commandParameters; - } - - - public boolean hasCommandParameters() { - return (getCommandParametersCount() > 0); - } - - public String toString() { return name(); }