Sync today's driving totals every 30 minutes
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# 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
|
## 2.2.38
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
52f58d348874fc62a5eefd470686583677769ff55bfe82c234c4f636769a0a90 FatihsMG4-unstable-2.2.39.apk
|
||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.evsuite.abrp"
|
applicationId "com.evsuite.abrp"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 55
|
versionCode 56
|
||||||
versionName "2.2.38"
|
versionName "2.2.39"
|
||||||
|
|
||||||
// MongoDB credentials never ship in the APK. The app only talks to the small
|
// MongoDB credentials never ship in the APK. The app only talks to the small
|
||||||
// authenticated API in server/, which owns the database connection.
|
// authenticated API in server/, which owns the database connection.
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
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 {
|
final class ConsumptionCloudClient {
|
||||||
private static final String TAG = "FatihsMG4.Mongo";
|
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 int TIMEOUT_MS = 8_000;
|
||||||
private static final String PREFS = "consumption_cloud";
|
private static final String PREFS = "consumption_cloud";
|
||||||
private static final String INSTALLATION_ID = "installation_id";
|
private static final String INSTALLATION_ID = "installation_id";
|
||||||
@@ -30,7 +31,9 @@ final class ConsumptionCloudClient {
|
|||||||
private final String token;
|
private final String token;
|
||||||
private final String installationId;
|
private final String installationId;
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
private long lastAttemptMs;
|
private long lastConsumptionAttemptMs;
|
||||||
|
private long lastChargeAttemptMs;
|
||||||
|
private String lastAttemptedChargeId = "";
|
||||||
|
|
||||||
ConsumptionCloudClient(Context context) {
|
ConsumptionCloudClient(Context context) {
|
||||||
endpoint = BuildConfig.CONSUMPTION_API_URL.trim();
|
endpoint = BuildConfig.CONSUMPTION_API_URL.trim();
|
||||||
@@ -51,14 +54,15 @@ final class ConsumptionCloudClient {
|
|||||||
*/
|
*/
|
||||||
void syncIfDue(ConsumptionTracker tracker, ChargeSessionTracker charges, long nowMs) {
|
void syncIfDue(ConsumptionTracker tracker, ChargeSessionTracker charges, long nowMs) {
|
||||||
long elapsed = android.os.SystemClock.elapsedRealtime();
|
long elapsed = android.os.SystemClock.elapsedRealtime();
|
||||||
if (endpoint.isEmpty() || token.isEmpty()
|
if (endpoint.isEmpty() || token.isEmpty()) return;
|
||||||
|| (lastAttemptMs != 0L && elapsed - lastAttemptMs < SYNC_INTERVAL_MS)) return;
|
syncPendingChargesIfDue(charges, elapsed);
|
||||||
lastAttemptMs = elapsed;
|
if (lastConsumptionAttemptMs != 0L
|
||||||
|
&& elapsed - lastConsumptionAttemptMs < CONSUMPTION_SYNC_INTERVAL_MS) return;
|
||||||
|
lastConsumptionAttemptMs = elapsed;
|
||||||
try {
|
try {
|
||||||
syncDailyConsumption(tracker, nowMs);
|
syncDailyConsumption(tracker, nowMs);
|
||||||
syncPendingCharges(charges);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Cloud sync unavailable");
|
Log.w(TAG, "Consumption sync unavailable");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +74,22 @@ final class ConsumptionCloudClient {
|
|||||||
catch (RuntimeException ignored) { }
|
catch (RuntimeException ignored) { }
|
||||||
for (int sent = 0; sent < MAX_DAYS_PER_SYNC; sent++) {
|
for (int sent = 0; sent < MAX_DAYS_PER_SYNC; sent++) {
|
||||||
LocalDate target = tracker.nextStoredDayAfter(lastUploaded);
|
LocalDate target = tracker.nextStoredDayAfter(lastUploaded);
|
||||||
if (target == null) return;
|
if (target == null) break;
|
||||||
ConsumptionTracker.CloudSnapshot snapshot = tracker.cloudSnapshot(target);
|
ConsumptionTracker.CloudSnapshot snapshot = tracker.cloudSnapshot(target);
|
||||||
|
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 JSONObject consumptionBody(ConsumptionTracker.CloudSnapshot snapshot, long nowMs)
|
||||||
|
throws Exception {
|
||||||
JSONObject body = new JSONObject()
|
JSONObject body = new JSONObject()
|
||||||
.put("installationId", installationId)
|
.put("installationId", installationId)
|
||||||
.put("recordedAt", nowMs)
|
.put("recordedAt", nowMs)
|
||||||
@@ -79,14 +97,27 @@ final class ConsumptionCloudClient {
|
|||||||
.put("day", totals(snapshot.day))
|
.put("day", totals(snapshot.day))
|
||||||
.put("lifetime", totals(snapshot.lifetime));
|
.put("lifetime", totals(snapshot.lifetime));
|
||||||
if (snapshot.sohPercent != null) body.put("sohPercent", snapshot.sohPercent);
|
if (snapshot.sohPercent != null) body.put("sohPercent", snapshot.sohPercent);
|
||||||
if (!send("/v1/consumption/daily", "PUT", body)) return;
|
return body;
|
||||||
prefs.edit().putString(LAST_CONSUMPTION_DATE, target.toString()).commit();
|
}
|
||||||
lastUploaded = target;
|
|
||||||
|
private void syncPendingChargesIfDue(ChargeSessionTracker charges, long elapsed) {
|
||||||
|
List<JSONObject> 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) throws Exception {
|
private void syncPendingCharges(ChargeSessionTracker charges, List<JSONObject> pending)
|
||||||
List<JSONObject> pending = charges.pendingUploads();
|
throws Exception {
|
||||||
int sent = 0;
|
int sent = 0;
|
||||||
for (JSONObject session : pending) {
|
for (JSONObject session : pending) {
|
||||||
JSONObject body = new JSONObject(session.toString()).put("installationId", installationId);
|
JSONObject body = new JSONObject(session.toString()).put("installationId", installationId);
|
||||||
|
|||||||
Reference in New Issue
Block a user