From 056fc2a5bd1999b2d981bf2bf2f8afe7763f8212 Mon Sep 17 00:00:00 2001 From: TCFDONMEZ Date: Thu, 8 Oct 2026 16:46:47 +0300 Subject: [PATCH] Sync today's driving totals every 30 minutes --- CHANGELOG.md | 9 +++ FatihsMG4-unstable-2.2.39.apk.sha256 | 1 + app/build.gradle | 4 +- .../evsuite/abrp/ConsumptionCloudClient.java | 65 ++++++++++++++----- 4 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 FatihsMG4-unstable-2.2.39.apk.sha256 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc3d4a..dcb90af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.2.39 + +### Changed + +- Today's cumulative driving totals are now upserted to MongoDB every 30 minutes instead + of waiting until the following day. Re-sending totals cannot double-count a retry. +- DC charging sessions remain entirely local while charging and are sent immediately after + completion. Failed completed-session uploads remain queued and retry every five minutes. + ## 2.2.38 ### Changed diff --git a/FatihsMG4-unstable-2.2.39.apk.sha256 b/FatihsMG4-unstable-2.2.39.apk.sha256 new file mode 100644 index 0000000..4f61d72 --- /dev/null +++ b/FatihsMG4-unstable-2.2.39.apk.sha256 @@ -0,0 +1 @@ +52f58d348874fc62a5eefd470686583677769ff55bfe82c234c4f636769a0a90 FatihsMG4-unstable-2.2.39.apk diff --git a/app/build.gradle b/app/build.gradle index e3951f1..79e31a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.evsuite.abrp" minSdk 27 targetSdk 36 - versionCode 55 - versionName "2.2.38" + versionCode 56 + versionName "2.2.39" // MongoDB credentials never ship in the APK. The app only talks to the small // authenticated API in server/, which owns the database connection. diff --git a/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java b/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java index e6b16f3..e085061 100644 --- a/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java +++ b/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java @@ -15,10 +15,11 @@ import java.time.LocalDate; import java.util.List; import java.util.UUID; -/** Best-effort MongoDB sync through the API in server/. Never blocks local persistence. */ +/** Best-effort MongoDB sync through the web API. Never blocks local persistence. */ final class ConsumptionCloudClient { private static final String TAG = "FatihsMG4.Mongo"; - private static final long SYNC_INTERVAL_MS = 5 * 60_000L; + private static final long CONSUMPTION_SYNC_INTERVAL_MS = 30 * 60_000L; + private static final long CHARGE_RETRY_INTERVAL_MS = 5 * 60_000L; private static final int TIMEOUT_MS = 8_000; private static final String PREFS = "consumption_cloud"; private static final String INSTALLATION_ID = "installation_id"; @@ -30,7 +31,9 @@ final class ConsumptionCloudClient { private final String token; private final String installationId; private final SharedPreferences prefs; - private long lastAttemptMs; + private long lastConsumptionAttemptMs; + private long lastChargeAttemptMs; + private String lastAttemptedChargeId = ""; ConsumptionCloudClient(Context context) { endpoint = BuildConfig.CONSUMPTION_API_URL.trim(); @@ -51,14 +54,15 @@ final class ConsumptionCloudClient { */ void syncIfDue(ConsumptionTracker tracker, ChargeSessionTracker charges, long nowMs) { long elapsed = android.os.SystemClock.elapsedRealtime(); - if (endpoint.isEmpty() || token.isEmpty() - || (lastAttemptMs != 0L && elapsed - lastAttemptMs < SYNC_INTERVAL_MS)) return; - lastAttemptMs = elapsed; + if (endpoint.isEmpty() || token.isEmpty()) return; + syncPendingChargesIfDue(charges, elapsed); + if (lastConsumptionAttemptMs != 0L + && elapsed - lastConsumptionAttemptMs < CONSUMPTION_SYNC_INTERVAL_MS) return; + lastConsumptionAttemptMs = elapsed; try { syncDailyConsumption(tracker, nowMs); - syncPendingCharges(charges); } catch (Exception e) { - Log.w(TAG, "Cloud sync unavailable"); + Log.w(TAG, "Consumption sync unavailable"); } } @@ -70,23 +74,50 @@ final class ConsumptionCloudClient { catch (RuntimeException ignored) { } for (int sent = 0; sent < MAX_DAYS_PER_SYNC; sent++) { LocalDate target = tracker.nextStoredDayAfter(lastUploaded); - if (target == null) return; + if (target == null) break; ConsumptionTracker.CloudSnapshot snapshot = tracker.cloudSnapshot(target); - JSONObject body = new JSONObject() - .put("installationId", installationId) - .put("recordedAt", nowMs) - .put("date", snapshot.date) - .put("day", totals(snapshot.day)) - .put("lifetime", totals(snapshot.lifetime)); - if (snapshot.sohPercent != null) body.put("sohPercent", snapshot.sohPercent); + JSONObject body = consumptionBody(snapshot, nowMs); if (!send("/v1/consumption/daily", "PUT", body)) return; prefs.edit().putString(LAST_CONSUMPTION_DATE, target.toString()).commit(); lastUploaded = target; } + // Today's cumulative counters overwrite the same installation/date document every + // 30 minutes. Sending totals rather than increments makes retries idempotent. + ConsumptionTracker.CloudSnapshot today = tracker.cloudSnapshot(LocalDate.now()); + JSONObject current = consumptionBody(today, nowMs); + send("/v1/consumption/daily", "PUT", current); } - private void syncPendingCharges(ChargeSessionTracker charges) throws Exception { + private JSONObject consumptionBody(ConsumptionTracker.CloudSnapshot snapshot, long nowMs) + throws Exception { + JSONObject body = new JSONObject() + .put("installationId", installationId) + .put("recordedAt", nowMs) + .put("date", snapshot.date) + .put("day", totals(snapshot.day)) + .put("lifetime", totals(snapshot.lifetime)); + if (snapshot.sohPercent != null) body.put("sohPercent", snapshot.sohPercent); + return body; + } + + private void syncPendingChargesIfDue(ChargeSessionTracker charges, long elapsed) { List pending = charges.pendingUploads(); + if (pending.isEmpty()) return; + String firstId = pending.get(0).optString("sessionId", ""); + boolean newlyCompleted = !firstId.equals(lastAttemptedChargeId); + if (!newlyCompleted && lastChargeAttemptMs != 0L + && elapsed - lastChargeAttemptMs < CHARGE_RETRY_INTERVAL_MS) return; + lastAttemptedChargeId = firstId; + lastChargeAttemptMs = elapsed; + try { + syncPendingCharges(charges, pending); + } catch (Exception e) { + Log.w(TAG, "Charging-session sync unavailable"); + } + } + + private void syncPendingCharges(ChargeSessionTracker charges, List pending) + throws Exception { int sent = 0; for (JSONObject session : pending) { JSONObject body = new JSONObject(session.toString()).put("installationId", installationId);