From 31ef600d6592899172e29a85c2a56058a307cd09 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 28 Jul 2024 01:38:28 +0200 Subject: [PATCH 1/8] Handling Temp Basal events in NightscoutTreatments. WIP - as we don't have NS profile yet. --- .../utilitymodels/NightscoutTreatments.java | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java index 643bfc08da..5dbb51ce3c 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java @@ -1,18 +1,22 @@ package com.eveningoutpost.dexdrip.utilitymodels; import com.eveningoutpost.dexdrip.Home; +import com.eveningoutpost.dexdrip.NewDataObserver; import com.eveningoutpost.dexdrip.models.BloodTest; import com.eveningoutpost.dexdrip.models.DateUtil; import com.eveningoutpost.dexdrip.models.InsulinInjection; import com.eveningoutpost.dexdrip.models.JoH; import com.eveningoutpost.dexdrip.models.Treatments; import com.eveningoutpost.dexdrip.models.UserError; +import com.eveningoutpost.dexdrip.models.APStatus; +import org.checkerframework.checker.units.qual.A; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.UUID; @@ -22,7 +26,7 @@ public class NightscoutTreatments { - private static final boolean d = false; + private static final boolean d = true; private static final String TAG = "NightscoutTreatments"; private static final HashSet bad_uuids = new HashSet<>(); @@ -32,6 +36,7 @@ public static boolean processTreatmentResponse(final String response) throws Exc boolean new_data = false; final JSONArray jsonArray = new JSONArray(response); + ArrayList statusEntries = new ArrayList(); for (int i = 0; i < jsonArray.length(); i++) { final JSONObject tr = (JSONObject) jsonArray.get(i); @@ -44,7 +49,7 @@ public static boolean processTreatmentResponse(final String response) throws Exc continue; } if (d) - UserError.Log.d(TAG, "event: " + etype + "_id: " + nightscout_id + " uuid:" + uuid); + UserError.Log.d(TAG, "event: " + etype + " _id: " + nightscout_id + " uuid: " + uuid); boolean from_xdrip = false; try { @@ -99,6 +104,7 @@ public static boolean processTreatmentResponse(final String response) throws Exc // extract treatment data if present double carbs = 0; double insulin = 0; + Double absolute = null; String injections = null; String notes = null; try { @@ -111,6 +117,11 @@ public static boolean processTreatmentResponse(final String response) throws Exc } catch (JSONException e) { // Log.d(TAG, "json processing: " + e); } + try { + absolute = tr.has("absolute") ? tr.getDouble("absolute") : null; + } catch (JSONException e) { + UserError.Log.e(TAG, "Could not parse absolute: " + tr.get("absolute")); + } try { injections = tr.getString("insulinInjections"); } catch (JSONException e) { @@ -125,11 +136,27 @@ public static boolean processTreatmentResponse(final String response) throws Exc if ((notes != null) && ((notes.startsWith("AndroidAPS started") || notes.equals("null") || (notes.equals("Bolus Std"))))) notes = null; - if ((carbs > 0) || (insulin > 0) || (notes != null)) { + if ((carbs > 0) || (insulin > 0) || (notes != null) || (absolute != null && etype.equals("Temp Basal"))) { + final long timestamp = DateUtil.tolerantFromISODateString(tr.getString("created_at")).getTime(); + if (timestamp > 0) { if (d) - UserError.Log.d(TAG, "Treatment: Carbs: " + carbs + " Insulin: " + insulin + " timestamp: " + timestamp); + UserError.Log.d(TAG, "Treatment: Carbs: " + carbs + " Insulin: " + insulin + " Temp basal: " + absolute + " timestamp: " + timestamp); + + if (absolute != null) { + UserError.Log.ueh(TAG, "New Treatment from Nightscout: Temp Basal: " + absolute + " Event type: " + etype + " timestamp: " + JoH.dateTimeText(timestamp) + ((notes != null) ? " Note: " + notes : "")); + + if (etype.equals("Temp Basal")) { + statusEntries.add(new APStatusEntry(absolute, timestamp)); + new_data = true; + } else { + UserError.Log.e(TAG, "Event type is not for Temp Basal: " + etype); + } + } else if (etype.equals("Temp Basal")) { + UserError.Log.e(TAG, "Could not parse rate: " + tr.getDouble("rate")); + } + Treatments existing = Treatments.byuuid(nightscout_id); if (existing == null) existing = Treatments.byuuid(uuid); @@ -205,6 +232,29 @@ public static boolean processTreatmentResponse(final String response) throws Exc } } } + + statusEntries.sort(APStatusEntry::compare); + + statusEntries.forEach(entry -> { + int temporaryPercentWithoutProfile = (int) (entry.absolute * 100 / 0.15); + APStatus.createEfficientRecord(entry.timestamp, temporaryPercentWithoutProfile, entry.absolute); + NewDataObserver.newExternalStatus(false); + }); + return new_data; } } + +class APStatusEntry { + double absolute; + long timestamp; + + public APStatusEntry(double absolute, long timestamp) { + this.absolute = absolute; + this.timestamp = timestamp; + } + + public static int compare(APStatusEntry a, APStatusEntry b) { + return (int) (a.timestamp - b.timestamp); + } +} \ No newline at end of file From 0ef380ec4c98e8b25782756375367bc527c87af2 Mon Sep 17 00:00:00 2001 From: Jan Date: Sun, 28 Jul 2024 19:54:59 +0200 Subject: [PATCH 2/8] =?UTF-8?q?Working=20profile=20loading=20from=20Nights?= =?UTF-8?q?cout.=20Not=20working=20yet=20=E2=80=93=20showing=20it=20in=20t?= =?UTF-8?q?he=20view.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cgm/nsfollow/NightscoutFollow.java | 33 ++++++++++ .../cgm/nsfollow/NightscoutFollowService.java | 2 +- .../dexdrip/cgm/nsfollow/Session.java | 8 +++ .../nsfollow/messages/BasalProfileEntry.java | 20 +++++++ .../cgm/nsfollow/messages/Profile.java | 60 +++++++++++++++++++ .../cgm/nsfollow/messages/SingleProfile.java | 10 ++++ .../utilitymodels/NightscoutTreatments.java | 24 +++----- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/pref_data_source.xml | 7 ++- 9 files changed, 148 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/BasalProfileEntry.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/Profile.java create mode 100644 app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/SingleProfile.java diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollow.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollow.java index eb3944db37..82256531e1 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollow.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollow.java @@ -1,8 +1,10 @@ package com.eveningoutpost.dexdrip.cgm.nsfollow; import com.eveningoutpost.dexdrip.BuildConfig; +import com.eveningoutpost.dexdrip.cgm.nsfollow.messages.Profile; import com.eveningoutpost.dexdrip.models.JoH; import com.eveningoutpost.dexdrip.models.UserError; +import com.eveningoutpost.dexdrip.profileeditor.BasalProfile; import com.eveningoutpost.dexdrip.utilitymodels.CollectionServiceStarter; import com.eveningoutpost.dexdrip.utilitymodels.Constants; import com.eveningoutpost.dexdrip.utilitymodels.NightscoutTreatments; @@ -36,6 +38,7 @@ import retrofit2.http.Query; import static com.eveningoutpost.dexdrip.models.JoH.emptyString; +import static com.eveningoutpost.dexdrip.profileeditor.BasalProfile.consolidate; import static com.eveningoutpost.dexdrip.utilitymodels.BgGraphBuilder.DEXCOM_PERIOD; import static com.eveningoutpost.dexdrip.utilitymodels.OkHttpWrapper.enableTls12OnPreLollipop; import static com.eveningoutpost.dexdrip.cgm.nsfollow.NightscoutFollowService.msg; @@ -66,6 +69,9 @@ public interface Nightscout { @GET("/api/v1/treatments") Call getTreatments(@Header("api-secret") String secret); + + @GET("/api/v1/profile") + Call> getProfiles(@Header("api-secret") String secret); } private static Nightscout getService() { @@ -109,6 +115,17 @@ public static void work(final boolean live) { }) .setOnFailure(() -> msg(session.treatmentsCallback.getStatus())); + // set up processing callback for profiles + session.profilesCallback = new NightscoutCallback>("NS profiles download", session, () -> { + // process data + try { + BasalProfile.save(BasalProfile.getActiveRateName(), session.currentProfile.getDefaultBasalProfile()); + } catch (Exception e) { + msg("Profile: " + e); + } + }) + .setOnFailure(() -> msg(session.profilesCallback.getStatus())); + if (!emptyString(urlString)) { try { int count = Math.min(MissedReadingsEstimator.estimate() + 1, (int) (Constants.DAY_IN_MS / DEXCOM_PERIOD)); @@ -119,6 +136,18 @@ public static void work(final boolean live) { UserError.Log.e(TAG, "Exception in entries work() " + e); msg("Nightscout follow entries error: " + e); } + + if (profileDownloadEnabled()) { + if (JoH.ratelimit("nsfollow-profile-download", 60)) { + try { + getService().getProfiles(session.url.getHashedSecret()).enqueue(session.profilesCallback); + } catch (Exception e) { + UserError.Log.e(TAG, "Exception in profiles work() " + e); + msg("Nightscout follow profiles error: " + e); + } + } + } + if (treatmentDownloadEnabled()) { if (JoH.ratelimit("nsfollow-treatment-download", 60)) { try { @@ -142,6 +171,10 @@ static boolean treatmentDownloadEnabled() { return Pref.getBooleanDefaultFalse("nsfollow_download_treatments"); } + static boolean profileDownloadEnabled() { + return Pref.getBoolean("nsfollow_download_profile", true); + } + public static final TypeAdapter UNRELIABLE_INTEGER = new TypeAdapter() { @Override public Number read(JsonReader in) throws IOException { diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollowService.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollowService.java index 720182618b..e0dfcc160b 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollowService.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/NightscoutFollowService.java @@ -44,7 +44,7 @@ public class NightscoutFollowService extends ForegroundService { private static final String TAG = "NightscoutFollow"; - private static final long SAMPLE_PERIOD = DEXCOM_PERIOD; + private static final long SAMPLE_PERIOD = 60_000; protected static volatile String lastState = ""; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/Session.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/Session.java index 0839430cef..b15b645f97 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/Session.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/Session.java @@ -1,6 +1,7 @@ package com.eveningoutpost.dexdrip.cgm.nsfollow; import com.eveningoutpost.dexdrip.cgm.nsfollow.messages.Entry; +import com.eveningoutpost.dexdrip.cgm.nsfollow.messages.Profile; import com.eveningoutpost.dexdrip.cgm.nsfollow.utils.NightscoutUrl; import java.util.List; @@ -20,12 +21,14 @@ public class Session { public NightscoutUrl url; public BaseCallback> entriesCallback; public BaseCallback treatmentsCallback; + public BaseCallback> profilesCallback; // most recent set of entries public List entries; // most recent treatments raw json public ResponseBody treatments; + public Profile currentProfile; // populate session data from a response object which could be any supported type @@ -36,6 +39,11 @@ public void populate(final Object object) { if (!someList.isEmpty() && someList.get(0) instanceof Entry) { entries = (List)object; } + if (!someList.isEmpty() && someList.get(0) instanceof Profile) { + List list = (List)object; + list.sort(Profile::compare); + currentProfile = list.get(0); + } } else if (object instanceof ResponseBody) { treatments = (ResponseBody)object; diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/BasalProfileEntry.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/BasalProfileEntry.java new file mode 100644 index 0000000000..39c0f08466 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/BasalProfileEntry.java @@ -0,0 +1,20 @@ +package com.eveningoutpost.dexdrip.cgm.nsfollow.messages; + +import com.google.gson.annotations.Expose; + +import java.util.Locale; + +public class BasalProfileEntry { + @Expose + public String time; // 01:00 etc. + @Expose + public int timeAsSeconds; + @Expose + public double value; + + public BasalProfileEntry(int timeAsSeconds, double value) { + this.timeAsSeconds = timeAsSeconds; + this.value = value; + this.time = String.format((Locale) null, "%02d:00", timeAsSeconds / 3600); + } +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/Profile.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/Profile.java new file mode 100644 index 0000000000..5f67e5e005 --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/Profile.java @@ -0,0 +1,60 @@ +package com.eveningoutpost.dexdrip.cgm.nsfollow.messages; + +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +public class Profile extends BaseMessage { + @Expose + public String _id; + @Expose + public String defaultProfile; + @Expose + public String startDate; // ISO date time + @Expose + public String created_at; // ISO date time + @Expose + public HashMap store; + + public static int compare(Profile a, Profile b) { + return b.startDate.compareTo(a.startDate); + } + + public List getDefaultBasalProfile() { + SingleProfile singleProfile = store.get("defaultProfile"); + + if (singleProfile == null) { + return new ArrayList<>(); + } + + ArrayList profileFromNS = singleProfile.basal; + + int oneHourAsSeconds = 3600; + + int size = profileFromNS.size(); + + for (int i = 0; profileFromNS.size() >= i + 1; i++) { + int nextIndex = i + 1; + + if (profileFromNS.size() <= nextIndex) { + break; + } + + BasalProfileEntry profileEntry = profileFromNS.get(i); + BasalProfileEntry nextProfileEntry = profileFromNS.get(i + 1); + + int nextTimeAsSeconds = profileEntry.timeAsSeconds + oneHourAsSeconds; + + if (nextProfileEntry.timeAsSeconds > nextTimeAsSeconds) { + profileFromNS.add(nextIndex, new BasalProfileEntry(nextTimeAsSeconds, profileEntry.value)); + } + } + + return profileFromNS.stream().map(x -> x.value) + .collect(Collectors.toList()); + } +} + diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/SingleProfile.java b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/SingleProfile.java new file mode 100644 index 0000000000..92aa7ffadc --- /dev/null +++ b/app/src/main/java/com/eveningoutpost/dexdrip/cgm/nsfollow/messages/SingleProfile.java @@ -0,0 +1,10 @@ +package com.eveningoutpost.dexdrip.cgm.nsfollow.messages; + +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; + +public class SingleProfile { + @Expose + public ArrayList basal; +} diff --git a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java index 5dbb51ce3c..011ba8b5f3 100644 --- a/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java +++ b/app/src/main/java/com/eveningoutpost/dexdrip/utilitymodels/NightscoutTreatments.java @@ -144,23 +144,16 @@ public static boolean processTreatmentResponse(final String response) throws Exc if (d) UserError.Log.d(TAG, "Treatment: Carbs: " + carbs + " Insulin: " + insulin + " Temp basal: " + absolute + " timestamp: " + timestamp); - if (absolute != null) { - UserError.Log.ueh(TAG, "New Treatment from Nightscout: Temp Basal: " + absolute + " Event type: " + etype + " timestamp: " + JoH.dateTimeText(timestamp) + ((notes != null) ? " Note: " + notes : "")); - - if (etype.equals("Temp Basal")) { - statusEntries.add(new APStatusEntry(absolute, timestamp)); - new_data = true; - } else { - UserError.Log.e(TAG, "Event type is not for Temp Basal: " + etype); - } - } else if (etype.equals("Temp Basal")) { - UserError.Log.e(TAG, "Could not parse rate: " + tr.getDouble("rate")); - } - Treatments existing = Treatments.byuuid(nightscout_id); if (existing == null) existing = Treatments.byuuid(uuid); - if ((existing == null) && !(from_xdrip && skip_from_xdrip)) { + + if (absolute != null && etype.equals("Temp Basal")) { + UserError.Log.ueh(TAG, "New Treatment from Nightscout: Temp Basal: " + absolute + " Event type: " + etype + " timestamp: " + JoH.dateTimeText(timestamp) + ((notes != null) ? " Note: " + notes : "")); + + statusEntries.add(new APStatusEntry(absolute, timestamp)); + new_data = true; + } else if ((existing == null) && !(from_xdrip && skip_from_xdrip)) { // check for close timestamp duplicates perhaps existing = Treatments.byTimestamp(timestamp, 60000); if (!((existing != null) && (JoH.roundDouble(existing.insulin, 2) == JoH.roundDouble(insulin, 2)) @@ -236,8 +229,7 @@ public static boolean processTreatmentResponse(final String response) throws Exc statusEntries.sort(APStatusEntry::compare); statusEntries.forEach(entry -> { - int temporaryPercentWithoutProfile = (int) (entry.absolute * 100 / 0.15); - APStatus.createEfficientRecord(entry.timestamp, temporaryPercentWithoutProfile, entry.absolute); + APStatus.createEfficientRecord(entry.timestamp, entry.absolute); NewDataObserver.newExternalStatus(false); }); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c3743c9e7..ca8bc5c619 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1340,6 +1340,8 @@ Also download treatments from Nightscout as follower Download Treatments Nightscout Follow delay + Also download basal profile from Nightscout as follower + Download Basal Profile