- kotlin changes

- disabled problem in Fragment for now
This commit is contained in:
Andy Rozman 2021-04-16 23:41:24 +01:00
parent 0d1ff8dd41
commit bb657a2498
10 changed files with 723 additions and 979 deletions

View file

@ -311,6 +311,13 @@ class MedtronicFragment : DaggerFragment() {
binding.lastBolus.text = "" binding.lastBolus.text = ""
} }
if (true) {
// base basal rate
binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") "
+ resourceHelper.gs(R.string.pump_basebasalrate, medtronicPumpPlugin.baseBasalRate))
binding.tempBasal.text = "??"
} else {
val pumpState = pumpSync.expectedPumpState() val pumpState = pumpSync.expectedPumpState()
// base basal rate // base basal rate
binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") " binding.baseBasalRate.text = ("(" + medtronicPumpStatus.activeProfileName + ") "
@ -318,6 +325,7 @@ class MedtronicFragment : DaggerFragment() {
binding.tempBasal.text = pumpState.temporaryBasal?.toStringFull(dateUtil) binding.tempBasal.text = pumpState.temporaryBasal?.toStringFull(dateUtil)
?: "" ?: ""
}
// battery // battery
if (medtronicPumpStatus.batteryType == BatteryType.None || medtronicPumpStatus.batteryVoltage == null) { if (medtronicPumpStatus.batteryType == BatteryType.None || medtronicPumpStatus.batteryVoltage == null) {

View file

@ -1499,10 +1499,10 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements Pump, Ril
for (BasalProfileEntry profileEntry : basalProfile.getEntries()) { for (BasalProfileEntry profileEntry : basalProfile.getEntries()) {
if (profileEntry.rate > medtronicPumpStatus.maxBasal) { if (profileEntry.getRate() > medtronicPumpStatus.maxBasal) {
stringBuilder.append(profileEntry.startTime.toString("HH:mm")); stringBuilder.append(profileEntry.getStartTime().toString("HH:mm"));
stringBuilder.append("="); stringBuilder.append("=");
stringBuilder.append(profileEntry.rate); stringBuilder.append(profileEntry.getRate());
} }
} }

View file

@ -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<String, PumpSettingDTO> decodeSettingsLoop(byte[] rd) {
Map<String, PumpSettingDTO> 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<String, PumpSettingDTO> decodeSettings512(byte[] rd) {
Map<String, PumpSettingDTO> 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<String, PumpSettingDTO> map) {
map.put(key, new PumpSettingDTO(key, value, group));
}
public Map<String, PumpSettingDTO> decodeSettings(byte[] rd) {
Map<String, PumpSettingDTO> 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<String, PumpSettingDTO> 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));
}
}

View file

@ -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<String, PumpSettingDTO> {
val map: MutableMap<String, PumpSettingDTO> = 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<String, PumpSettingDTO> {
val map: MutableMap<String, PumpSettingDTO> = 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<String, PumpSettingDTO>) {
map[key] = PumpSettingDTO(key, value, group)
}
fun decodeSettings(rd: ByteArray): Map<String, PumpSettingDTO> {
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<String, PumpSettingDTO>) {
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)
}
}

View file

@ -10,7 +10,9 @@ import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms.CGMSH
import okhttp3.internal.and import okhttp3.internal.and
import org.joda.time.LocalDateTime import org.joda.time.LocalDateTime
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.* 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 * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes
@ -264,6 +266,6 @@ class MedtronicCGMSHistoryDecoder : MedtronicHistoryDecoder<CGMSHistoryEntry>()
} }
companion object { companion object {
private val LOG: Logger = getLogger(LTag.PUMPCOMM) private val LOG: Logger = getLogger1("MedtronicCGMSHistoryDecoder")
} }
} }

View file

@ -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.
* <p>
* 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
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<BasalProfileEntry> 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<BasalProfileEntry> 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<BasalProfileEntry> 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<BasalProfileEntry> 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<BasalProfileEntry> 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<BasalProfileEntry> getEntries() {
List<BasalProfileEntry> 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<Byte> 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<BasalProfileEntry> 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;
}
}

View file

@ -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<BasalProfileEntry>? = 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<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
}
/**
* 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<Byte> = 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<Double?> {
var entries: List<BasalProfileEntry>? = 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<Double>(24)
for (i in 0..23) {
basalByHour[i] = 0.0
}
return basalByHour
}
val basalByHour = arrayOfNulls<Double>(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<Double?>): String {
val stringBuilder = StringBuilder()
for (value in data) {
stringBuilder.append(String.format("%.3f", value))
stringBuilder.append(" ")
}
return stringBuilder.toString()
}
}
}

View file

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

View file

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

View file

@ -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, MedtronicCommandType(int code, String description, MedtronicDeviceType devices,
MinimedCommandParameterType parameterType, byte[] cmd_params) { MinimedCommandParameterType parameterType, byte[] cmd_params) {
this(code, description, devices, parameterType, 0, 1, 0, 0, 11, 0); 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); 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 // NEW
MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // MedtronicCommandType(int code, String description, MedtronicDeviceType devices, //
MinimedCommandParameterType parameterType, int expectedLength) { 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() { public byte getCommandCode() {
return commandCode; 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() { public String toString() {
return name(); return name();
} }