From 66fed48aaebccdb708afa5d3806c58aefbef662f Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Sun, 18 Oct 2026 13:12:12 +0200 Subject: [PATCH] App 2.12.1: no scroll bars; energy since start The pages' scroll bars never faded on the car: a ScrollView shows its bar on every layout, and the live values lay the page out every two seconds. StableScrollView, the root of every page, now has none. A new "Energy since start" tile, left of "Usage since start", shows the net kWh taken from the pack since the trip began, recuperation counted back. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki --- SPECS.md | 3 +++ app/build.gradle | 4 ++-- .../main/java/com/evsuite/abrp/MainActivity.java | 8 +++++++- .../main/java/com/evsuite/abrp/RangeEstimator.java | 5 +++++ .../java/com/evsuite/abrp/StableScrollView.java | 14 ++++++++++++-- app/src/main/res/layout-w1000dp/pane_vehicle.xml | 6 +++++- app/src/main/res/layout/pane_vehicle.xml | 5 +++++ app/src/main/res/values/strings.xml | 4 ++++ .../java/com/evsuite/abrp/RangeEstimatorTest.java | 13 +++++++++++++ 9 files changed, 56 insertions(+), 6 deletions(-) diff --git a/SPECS.md b/SPECS.md index b2b7357..32ba765 100644 --- a/SPECS.md +++ b/SPECS.md @@ -113,6 +113,9 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside `kubectl apply -f k8s/`). Filled-in secrets live only there from now on. +- 2026-10-18 — App 2.12.1: no scroll bars (the 2-second refresh kept them from ever fading); + "Energy since start" tile (net kWh of the trip, recuperation counted back) left of "Usage since + start". - 2026-10-18 — App 2.12.0, server 2.5.0: settings entered on a phone. "Enter on phone" shows a QR code; the phone form on the server (approval password) takes the ABRP credentials, charging price and usable capacity, and the car collects them once (RFC 8628-style, like pairing; values diff --git a/app/build.gradle b/app/build.gradle index 1ddd708..15b2507 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 - versionCode 75 - versionName "2.12.0" + versionCode 76 + versionName "2.12.1" // Nothing about the server ships in the APK: its URL and ingest token are entered in // the app's Server tab (ServerConfig), and the database is only reachable by the server. diff --git a/app/src/main/java/com/evsuite/abrp/MainActivity.java b/app/src/main/java/com/evsuite/abrp/MainActivity.java index 9372425..883b6fc 100644 --- a/app/src/main/java/com/evsuite/abrp/MainActivity.java +++ b/app/src/main/java/com/evsuite/abrp/MainActivity.java @@ -92,7 +92,8 @@ public class MainActivity extends AppCompatActivity { vehCharging, vehHvac, vehTireFl, vehTireFr, vehTireRl, vehTireRr, vehRangeTrip, vehRangeTripNote, vehRangeAverage, vehRangeAverageNote, - vehUsageTrip, vehUsageTripNote, vehUsageAverage, vehUsageAverageNote; + vehUsageTrip, vehUsageTripNote, vehUsageAverage, vehUsageAverageNote, + vehEnergyTrip, vehEnergyTripNote; /** Usable pack capacity for the range estimates; the car does not publish it. */ static final String USABLE_KWH_KEY = "usable_kwh"; @@ -1187,6 +1188,8 @@ public class MainActivity extends AppCompatActivity { vehUsageTripNote = vehiclePane.findViewById(R.id.vehicle_usage_trip_note); vehUsageAverage = vehiclePane.findViewById(R.id.vehicle_usage_average); vehUsageAverageNote = vehiclePane.findViewById(R.id.vehicle_usage_average_note); + vehEnergyTrip = vehiclePane.findViewById(R.id.vehicle_energy_trip); + vehEnergyTripNote = vehiclePane.findViewById(R.id.vehicle_energy_trip_note); } private double usableKwh() { @@ -1204,6 +1207,9 @@ public class MainActivity extends AppCompatActivity { estimator.add(monotonicMs, s.getBatteryPowerKw(), s.getSpeedKmh()); Double remaining = RangeEstimator.remainingKwh(s.getSocPercent(), usableKwh()); RangeEstimator.Estimate trip = estimator.sinceStart(remaining); + Double energy = estimator.energySinceStart(); + vehEnergyTrip.setText(energy == null ? "—" : getString(R.string.energy_kwh, energy)); + vehEnergyTripNote.setText(energy == null ? R.string.energy_trip_waiting : R.string.energy_trip_note); showEstimate(vehRangeTrip, vehRangeTripNote, vehUsageTrip, vehUsageTripNote, trip, trip == null ? "" : getString(R.string.usage_over_distance, trip.distanceKm), R.string.range_trip_waiting); diff --git a/app/src/main/java/com/evsuite/abrp/RangeEstimator.java b/app/src/main/java/com/evsuite/abrp/RangeEstimator.java index c3aa60d..8d55ea3 100644 --- a/app/src/main/java/com/evsuite/abrp/RangeEstimator.java +++ b/app/src/main/java/com/evsuite/abrp/RangeEstimator.java @@ -110,6 +110,11 @@ final class RangeEstimator { tripLast = s; } + /** Net energy taken from the pack since start, in kWh; null before the trip's first sample. */ + synchronized Double energySinceStart() { + return tripLast == null ? null : tripEnergyKwh; + } + /** Range at the consumption since start; null until the trip has some distance behind it. */ synchronized Estimate sinceStart(Double remainingKwh) { if (remainingKwh == null || tripDistanceKm < MIN_DISTANCE_KM) return null; diff --git a/app/src/main/java/com/evsuite/abrp/StableScrollView.java b/app/src/main/java/com/evsuite/abrp/StableScrollView.java index d760775..bf56944 100644 --- a/app/src/main/java/com/evsuite/abrp/StableScrollView.java +++ b/app/src/main/java/com/evsuite/abrp/StableScrollView.java @@ -16,18 +16,28 @@ import android.widget.ScrollView; * Here only scrolling that is not part of a layout counts as the driver's position, and every * layout restores it. ScrollView.scrollTo clamps against the real content height, so a * position that no longer exists still ends at the bottom rather than past it. + * + * No scroll bar: a ScrollView shows its bar on every layout, and the live values lay the page + * out every two seconds, so on the car it never faded. The pages scroll by touch. */ public class StableScrollView extends ScrollView { private int wantedScrollY; private boolean inLayout; - public StableScrollView(Context context) { super(context); } + public StableScrollView(Context context) { + super(context); + setVerticalScrollBarEnabled(false); + } - public StableScrollView(Context context, AttributeSet attrs) { super(context, attrs); } + public StableScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + setVerticalScrollBarEnabled(false); + } public StableScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + setVerticalScrollBarEnabled(false); } @Override diff --git a/app/src/main/res/layout-w1000dp/pane_vehicle.xml b/app/src/main/res/layout-w1000dp/pane_vehicle.xml index 3ec4f97..c5d1457 100644 --- a/app/src/main/res/layout-w1000dp/pane_vehicle.xml +++ b/app/src/main/res/layout-w1000dp/pane_vehicle.xml @@ -32,7 +32,11 @@ - + + + + + diff --git a/app/src/main/res/layout/pane_vehicle.xml b/app/src/main/res/layout/pane_vehicle.xml index 707b73b..c75c93c 100644 --- a/app/src/main/res/layout/pane_vehicle.xml +++ b/app/src/main/res/layout/pane_vehicle.xml @@ -21,6 +21,11 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e75f2cf..8133840 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,6 +34,10 @@ Range since start Range at 5-min average Usage since start + Energy since start + %1$.1f kWh + net, after recuperation + once the car is on Usage, 5-min average %1$.1f kWh/100 km > %1$d km diff --git a/app/src/test/java/com/evsuite/abrp/RangeEstimatorTest.java b/app/src/test/java/com/evsuite/abrp/RangeEstimatorTest.java index cb442da..b50d822 100644 --- a/app/src/test/java/com/evsuite/abrp/RangeEstimatorTest.java +++ b/app/src/test/java/com/evsuite/abrp/RangeEstimatorTest.java @@ -115,4 +115,17 @@ public class RangeEstimatorTest { assertEquals(-6.25, regen.kwhPer100Km, 0.01); assertTrue(Double.isInfinite(regen.rangeKm)); } + + @Test + public void energySinceStartIsTheNetEnergyOfTheTrip() { + RangeEstimator r = new RangeEstimator(); + assertNull(r.energySinceStart()); + r.add(0, 3f, 0f); + assertEquals(0.0, r.energySinceStart(), 1e-9); // the trip has begun + for (long t = 15_000; t <= 600_000; t += 15_000) r.add(t, 30f, 100f); + // 10 minutes at 30 kW = 5 kWh, plus the first 15 s ramp from 3 kW. + assertEquals(5.0, r.energySinceStart(), 0.1); + for (long t = 615_000; t <= 735_000; t += 15_000) r.add(t, -20f, 60f); // downhill + assertTrue(r.energySinceStart() < 5.0); // recuperation counts back + } }