Skip to content

Commit

Permalink
CareFollow - v11 endpoint - First working version
Browse files Browse the repository at this point in the history
Implementation of new v11 cloud data endpoint:
- replace endpoint in case of EU with v11 endpoint
- new and modified structures
- update data processing for new structures
- add JsonAdapter and structures to proguard
  • Loading branch information
benceszasz committed Jan 17, 2025
1 parent 194b595 commit 2baa087
Show file tree
Hide file tree
Showing 18 changed files with 376 additions and 51 deletions.
11 changes: 10 additions & 1 deletion app/proguard-debug.pro
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,13 @@

-keep class com.newrelic.** { *; }
-dontwarn com.newrelic.**
-keepattributes Exceptions, Signature, InnerClasses, LineNumberTable, SourceFile, EnclosingMethod
-keepattributes Exceptions, Signature, InnerClasses, LineNumberTable, SourceFile, EnclosingMethod

-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.*
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.*
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.** { *; }
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.** { *; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
9 changes: 8 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,11 @@
-dontwarn com.newrelic.**
-keepattributes Exceptions, Signature, InnerClasses, LineNumberTable

-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.*
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.*
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.*
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.** { *; }
-keep class com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.** { *; }
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ static synchronized void processData(final RecentData recentData, final boolean
filteredSgList = new ArrayList<>();
for (SensorGlucose sg : recentData.sgs) {
//SG DateTime is null (sensor expired?)
if (sg != null && sg.datetimeAsDate != null) {
if (sg != null && sg.getDate() != null) {
filteredSgList.add(sg);
}
}
Expand All @@ -84,31 +84,31 @@ static synchronized void processData(final RecentData recentData, final boolean
sensor.save();

// place in order of oldest first
Collections.sort(filteredSgList, (o1, o2) -> o1.datetimeAsDate.compareTo(o2.datetimeAsDate));
Collections.sort(filteredSgList, (o1, o2) -> o1.getDate().compareTo(o2.getDate()));

for (final SensorGlucose sg : filteredSgList) {

//Not EPOCH 0 (warmup?)
if (sg.datetimeAsDate.getTime() > 1) {
if (sg.getDate().getTime() > 1) {

//Not in the future
if (sg.datetimeAsDate.getTime() < new Date().getTime() + 300_000) {
if (sg.getDate().getTime() < new Date().getTime() + 300_000) {

//Not 0 SG (not calibrated?)
if (sg.sg > 0) {

//newer than last BG
if (sg.datetimeAsDate.getTime() > lastBgTimestamp) {
if (sg.getDate().getTime() > lastBgTimestamp) {

if (sg.datetimeAsDate.getTime() > 0) {
if (sg.getDate().getTime() > 0) {

//New entry
if (BgReading.getForPreciseTimestamp(sg.datetimeAsDate.getTime(), 10_000) == null) {
if (BgReading.getForPreciseTimestamp(sg.getDate().getTime(), 10_000) == null) {
UserError.Log.d(TAG, "NEW NEW NEW New entry: " + sg.toS());

if (live) {
final BgReading bg = new BgReading();
bg.timestamp = sg.datetimeAsDate.getTime();
bg.timestamp = sg.getDate().getTime();
bg.calculated_value = (double) sg.sg;
bg.raw_data = SPECIAL_FOLLOWER_PLACEHOLDER;
bg.filtered_data = (double) sg.sg;
Expand Down Expand Up @@ -149,25 +149,25 @@ static synchronized void processData(final RecentData recentData, final boolean
//Filter markers
filteredMarkerList = new ArrayList<>();
for (Marker marker : recentData.markers) {
if (marker != null && marker.type != null && marker.dateTime != null) {
if (marker != null && marker.type != null && marker.getDate() != null) {
filteredMarkerList.add(marker);
}
}

if (filteredMarkerList.size() > 0) {
//sort markers by time
Collections.sort(filteredMarkerList, (o1, o2) -> o1.dateTime.compareTo(o2.dateTime));
Collections.sort(filteredMarkerList, (o1, o2) -> o1.getDate().compareTo(o2.getDate()));

//process markers one-by-one
for (Marker marker : filteredMarkerList) {

//FINGER BG
if (marker.isBloodGlucose() && Pref.getBooleanDefaultFalse("clfollow_download_finger_bgs")) {
//check required values
if (marker.value != null && !marker.value.equals(0)) {
if (marker.getBloodGlucose() != null && !marker.getBloodGlucose().equals(0)) {
//new blood test
if (BloodTest.getForPreciseTimestamp(marker.dateTime.getTime(), 10000) == null) {
BloodTest.create(marker.dateTime.getTime(), marker.value, SOURCE_CARELINK_FOLLOW);
if (BloodTest.getForPreciseTimestamp(marker.getDate().getTime(), 10000) == null) {
BloodTest.create(marker.getDate().getTime(), marker.getBloodGlucose(), SOURCE_CARELINK_FOLLOW);
}
}

Expand All @@ -186,24 +186,24 @@ static synchronized void processData(final RecentData recentData, final boolean
//Insulin
if (marker.type.equals(Marker.MARKER_TYPE_INSULIN)) {
carbs = 0;
if (marker.deliveredExtendedAmount != null && marker.deliveredFastAmount != null) {
insulin = marker.deliveredExtendedAmount + marker.deliveredFastAmount;
if (marker.getInsulinAmount() != null) {
insulin = marker.getInsulinAmount();
}
//SKIP if insulin = 0
if (insulin == 0) continue;
//Carbs
} else if (marker.type.equals(Marker.MARKER_TYPE_MEAL)) {
if (marker.amount != null) {
carbs = marker.amount;
if (marker.getCarbAmount() != null) {
carbs = marker.getCarbAmount();
}
insulin = 0;
//SKIP if carbs = 0
if (carbs == 0) continue;
}

//new Treatment
if (newTreatment(carbs, insulin, marker.dateTime.getTime())) {
t = Treatments.create(carbs, insulin, marker.dateTime.getTime());
if (newTreatment(carbs, insulin, marker.getDate().getTime())) {
t = Treatments.create(carbs, insulin, marker.getDate().getTime());
if (t != null) {
t.enteredBy = SOURCE_CARELINK_FOLLOW;
t.save();
Expand All @@ -222,7 +222,7 @@ static synchronized void processData(final RecentData recentData, final boolean
//PUMP INFO (Pump Status)
if (recentData.isNGP()) {
PumpStatus.setReservoir(recentData.reservoirRemainingUnits);
PumpStatus.setBattery(recentData.medicalDeviceBatteryLevelPercent);
PumpStatus.setBattery(recentData.getDeviceBatteryLevel());
if (recentData.activeInsulin != null)
PumpStatus.setBolusIoB(recentData.activeInsulin.amount);
PumpStatus.syncUpdate();
Expand All @@ -246,13 +246,13 @@ static synchronized void processData(final RecentData recentData, final boolean
//Active Notifications
if (recentData.notificationHistory.activeNotifications != null) {
for (ActiveNotification activeNotification : recentData.notificationHistory.activeNotifications) {
addNotification(activeNotification.dateTime, recentData.getDeviceFamily(), activeNotification.messageId, activeNotification.faultId);
addNotification(activeNotification.dateTime, recentData.getDeviceFamily(), activeNotification.getMessageId(), activeNotification.faultId);
}
}
//Cleared Notifications
if (recentData.notificationHistory.clearedNotifications != null) {
for (ClearedNotification clearedNotification : recentData.notificationHistory.clearedNotifications) {
addNotification(clearedNotification.triggeredDateTime, recentData.getDeviceFamily(), clearedNotification.messageId, clearedNotification.faultId);
addNotification(clearedNotification.triggeredDateTime, recentData.getDeviceFamily(), clearedNotification.getMessageId(), clearedNotification.faultId);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.ClearedNotification;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.CountrySettings;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.DataUpload;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.DisplayMessage;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.Marker;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.MonitorData;
import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.Profile;
Expand Down Expand Up @@ -53,6 +54,9 @@ public class CareLinkClient {
protected String carelinkCountry;
protected static final String CARELINK_CONNECT_SERVER_EU = "carelink.minimed.eu";
protected static final String CARELINK_CONNECT_SERVER_US = "carelink.minimed.com";
protected static final String CARELINK_CLOUD_SERVER_EU = "clcloud.minimed.eu";
protected static final String CARELINK_CLOUD_SERVER_US = "clcloud.minimed.com";
protected static final String API_PATH_DISPLAY_MESSAGE = "connect/carepartner/v11/display/message";
protected static final String CARELINK_LANGUAGE_EN = "en";
protected static final String CARELINK_AUTH_TOKEN_COOKIE_NAME = "auth_tmp_token";
protected static final String CARELINK_TOKEN_VALIDTO_COOKIE_NAME = "c_token_valid_to";
Expand Down Expand Up @@ -197,6 +201,12 @@ protected String careLinkServer() {
return CARELINK_CONNECT_SERVER_EU;
}

protected String cloudServer() {
if (this.carelinkCountry.equals("us"))
return CARELINK_CLOUD_SERVER_US;
else
return CARELINK_CLOUD_SERVER_EU;
}

//Wrapper for common request of recent data (last 24 hours)
public RecentData getRecentData() {
Expand Down Expand Up @@ -619,10 +629,14 @@ public RecentData getLast24Hours() {
// Periodic data from CareLink Cloud
public RecentData getConnectDisplayMessage(String username, String role, String patientUsername, String endpointUrl) {

RequestBody requestBody = null;
Gson gson = null;
JsonObject userJson = null;
RequestBody requestBody;
Gson gson;
JsonObject userJson;
RecentData recentData = null;
DisplayMessage displayMessage;
boolean useNewEndpoint;
HttpUrl newEndpointUrl;


// Build user json for request
userJson = new JsonObject();
Expand All @@ -635,10 +649,33 @@ public RecentData getConnectDisplayMessage(String username, String role, String

requestBody = RequestBody.create(MediaType.get("application/json; charset=utf-8"), gson.toJson(userJson));

//use new v11 endpoint outside US
useNewEndpoint = !this.carelinkCountry.equals("us") ? true : false;

//new endpoint url
newEndpointUrl = new HttpUrl.Builder()
.scheme("https")
.host(this.cloudServer())
.addPathSegments(API_PATH_DISPLAY_MESSAGE)
.build();

//get data and correct time
try {
recentData = this.getData(HttpUrl.parse(endpointUrl), requestBody, RecentData.class);
if (recentData != null)
correctTimeInRecentData(recentData);
//Use old data format for old endpoint
if (!useNewEndpoint) {
recentData = this.getData(HttpUrl.parse(endpointUrl), requestBody, RecentData.class);
if (recentData != null) {
correctTimeInRecentData(recentData);
}
}
//Use new data format outside US
else {
displayMessage = this.getData(newEndpointUrl, requestBody, DisplayMessage.class);
if (displayMessage != null && displayMessage.patientData != null) {
correctTimeInDisplayMessage(displayMessage);
recentData = displayMessage.patientData;
}
}
} catch (Exception e) {
lastErrorMessage = e.getClass().getSimpleName() + ":" + e.getMessage();
}
Expand Down Expand Up @@ -668,6 +705,11 @@ public RecentData getM2MPatientData(String patientUsername) {
}

// General data request for API calls

protected <T> T getData(String host, String path, RequestBody requestBody, Class<T> dataClass) {
return this.getData(new HttpUrl.Builder().scheme("https").host(host).addPathSegments(path).build(), requestBody, dataClass);
}

protected <T> T getData(HttpUrl url, RequestBody requestBody, Class<T> dataClass) {

Request.Builder requestBuilder = null;
Expand Down Expand Up @@ -774,6 +816,65 @@ protected void addHttpHeaders(Request.Builder requestBuilder, RequestType type,

}

protected void correctTimeInDisplayMessage(DisplayMessage displayMessage) {

boolean timezoneMissing = false;
String offsetString = null;
RecentData recentData = null;

recentData = displayMessage.patientData;

//time data is available to check and correct time if needed
if (recentData.lastConduitDateTime != null && recentData.lastConduitDateTime.getTime() > 1
&& recentData.lastConduitUpdateServerDateTime > 1) {

//Correct times if server <> device > 26 mins => possibly different time zones
int diffInHour = (int) Math.round(((recentData.lastConduitUpdateServerDateTime - recentData.lastConduitDateTime.getTime()) / 3600000D));
if (diffInHour != 0 && diffInHour < 26) {

recentData.lastConduitDateTime = shiftDateByHours(recentData.lastConduitDateTime, diffInHour);

//Sensor glucose
if (recentData.sgs != null) {
for (SensorGlucose sg : recentData.sgs) {
if(sg.timestamp != null)
sg.timestamp = shiftDateByHours(sg.timestamp, diffInHour);
}
}

//Markers
if (recentData.markers != null) {
for (Marker marker : recentData.markers) {
if(marker.timestamp != null)
marker.timestamp = shiftDateByHours(marker.timestamp, diffInHour);
}
}
//Notifications
if (recentData.notificationHistory != null) {
if (recentData.notificationHistory.clearedNotifications != null) {
for (ClearedNotification notification : recentData.notificationHistory.clearedNotifications) {
if(notification.dateTime != null) {
notification.dateTime = shiftDateByHours(notification.dateTime, diffInHour);
notification.triggeredDateTime = shiftDateByHours(notification.triggeredDateTime, diffInHour);
}
}
}
if (recentData.notificationHistory.activeNotifications != null) {
for (ActiveNotification notification : recentData.notificationHistory.activeNotifications) {
if(notification.dateTime != null)
notification.dateTime = shiftDateByHours(notification.dateTime, diffInHour);
}
}
}

}

}

displayMessage.patientData = recentData;

}

protected void correctTimeInRecentData(RecentData recentData) {

boolean timezoneMissing = false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.eveningoutpost.dexdrip.cgm.carelinkfollow.message;

import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.CareLinkJsonAdapter;
import com.google.gson.annotations.JsonAdapter;

import java.util.Date;

/**
Expand All @@ -8,6 +11,7 @@
public class ActiveInsulin {

public Integer code;
@JsonAdapter(CareLinkJsonAdapter.class)
public Date datetime;
public long version;
public Double amount;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package com.eveningoutpost.dexdrip.cgm.carelinkfollow.message;

import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.CareLinkJsonAdapter;
import com.google.gson.annotations.JsonAdapter;

import java.util.Date;

public class ActiveNotification {
public class ActiveNotification extends Notification {

public String GUID;
public Date dateTime;
public String type;
public int faultId;
public int instanceId;
public String messageId;
public String pumpDeliverySuspendState;
public String pnpId;
public int relativeOffset;

public Boolean alertSilenced;


}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.eveningoutpost.dexdrip.cgm.carelinkfollow.message;

import com.eveningoutpost.dexdrip.cgm.carelinkfollow.message.util.CareLinkJsonAdapter;
import com.google.gson.annotations.JsonAdapter;

import java.util.Date;

/**
Expand All @@ -12,6 +15,7 @@ public String getMessageAlarmCode() {
}

public int code;
//@JsonAdapter(CareLinkJsonAdapter.class)
public String datetime;
public Date datetimeAsDate;
public String type;
Expand Down
Loading

0 comments on commit 2baa087

Please sign in to comment.