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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -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
|
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
|
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.
|
`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
|
- 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
|
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
|
price and usable capacity, and the car collects them once (RFC 8628-style, like pairing; values
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 75
|
versionCode 76
|
||||||
versionName "2.12.0"
|
versionName "2.12.1"
|
||||||
|
|
||||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
// 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.
|
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
vehCharging, vehHvac,
|
vehCharging, vehHvac,
|
||||||
vehTireFl, vehTireFr, vehTireRl, vehTireRr,
|
vehTireFl, vehTireFr, vehTireRl, vehTireRr,
|
||||||
vehRangeTrip, vehRangeTripNote, vehRangeAverage, vehRangeAverageNote,
|
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. */
|
/** Usable pack capacity for the range estimates; the car does not publish it. */
|
||||||
static final String USABLE_KWH_KEY = "usable_kwh";
|
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);
|
vehUsageTripNote = vehiclePane.findViewById(R.id.vehicle_usage_trip_note);
|
||||||
vehUsageAverage = vehiclePane.findViewById(R.id.vehicle_usage_average);
|
vehUsageAverage = vehiclePane.findViewById(R.id.vehicle_usage_average);
|
||||||
vehUsageAverageNote = vehiclePane.findViewById(R.id.vehicle_usage_average_note);
|
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() {
|
private double usableKwh() {
|
||||||
@@ -1204,6 +1207,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
estimator.add(monotonicMs, s.getBatteryPowerKw(), s.getSpeedKmh());
|
estimator.add(monotonicMs, s.getBatteryPowerKw(), s.getSpeedKmh());
|
||||||
Double remaining = RangeEstimator.remainingKwh(s.getSocPercent(), usableKwh());
|
Double remaining = RangeEstimator.remainingKwh(s.getSocPercent(), usableKwh());
|
||||||
RangeEstimator.Estimate trip = estimator.sinceStart(remaining);
|
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,
|
showEstimate(vehRangeTrip, vehRangeTripNote, vehUsageTrip, vehUsageTripNote, trip,
|
||||||
trip == null ? "" : getString(R.string.usage_over_distance, trip.distanceKm),
|
trip == null ? "" : getString(R.string.usage_over_distance, trip.distanceKm),
|
||||||
R.string.range_trip_waiting);
|
R.string.range_trip_waiting);
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ final class RangeEstimator {
|
|||||||
tripLast = s;
|
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. */
|
/** Range at the consumption since start; null until the trip has some distance behind it. */
|
||||||
synchronized Estimate sinceStart(Double remainingKwh) {
|
synchronized Estimate sinceStart(Double remainingKwh) {
|
||||||
if (remainingKwh == null || tripDistanceKm < MIN_DISTANCE_KM) return null;
|
if (remainingKwh == null || tripDistanceKm < MIN_DISTANCE_KM) return null;
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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.
|
* 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 {
|
public class StableScrollView extends ScrollView {
|
||||||
|
|
||||||
private int wantedScrollY;
|
private int wantedScrollY;
|
||||||
private boolean inLayout;
|
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) {
|
public StableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
|
setVerticalScrollBarEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -32,7 +32,11 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:orientation="horizontal" android:baselineAligned="false">
|
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:orientation="horizontal" android:baselineAligned="false">
|
||||||
<Space android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" android:layout_marginEnd="10dp" />
|
<Space android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" android:layout_marginEnd="10dp" />
|
||||||
<Space android:layout_width="0dp" android:layout_height="1dp" android:layout_weight="1" android:layout_marginEnd="10dp" />
|
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_energy_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
||||||
|
<TextView android:id="@+id/vehicle_energy_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||||
|
<TextView android:id="@+id/vehicle_energy_trip_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||||
|
</LinearLayout>
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginEnd="10dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_usage_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_usage_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
||||||
<TextView android:id="@+id/vehicle_usage_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
<TextView android:id="@+id/vehicle_usage_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
<TextView android:id="@+id/vehicle_range_average" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||||
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
<TextView android:id="@+id/vehicle_range_average_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_energy_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
||||||
|
<TextView android:id="@+id/vehicle_energy_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||||
|
<TextView android:id="@+id/vehicle_energy_trip_note" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textAppearance="@style/Text.EV.Secondary" android:textColor="@color/ev_cyan" android:textSize="16sp" android:maxLines="1" android:ellipsize="end" />
|
||||||
|
</LinearLayout>
|
||||||
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
<LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_columnWeight="1" android:layout_margin="4dp" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass">
|
||||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_usage_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_usage_trip" android:textAppearance="@style/Text.EV.Secondary" />
|
||||||
<TextView android:id="@+id/vehicle_usage_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
<TextView android:id="@+id/vehicle_usage_trip" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="—" android:layout_marginTop="6dp" android:textAppearance="@style/Text.EV.Metric.Compact" android:maxLines="1" android:autoSizeTextType="uniform" android:autoSizeMinTextSize="18sp" android:autoSizeMaxTextSize="28sp" android:autoSizeStepGranularity="1sp" />
|
||||||
|
|||||||
@@ -34,6 +34,10 @@
|
|||||||
<string name="vehicle_row_range_trip">Range since start</string>
|
<string name="vehicle_row_range_trip">Range since start</string>
|
||||||
<string name="vehicle_row_range_average">Range at 5-min average</string>
|
<string name="vehicle_row_range_average">Range at 5-min average</string>
|
||||||
<string name="vehicle_row_usage_trip">Usage since start</string>
|
<string name="vehicle_row_usage_trip">Usage since start</string>
|
||||||
|
<string name="vehicle_row_energy_trip">Energy since start</string>
|
||||||
|
<string name="energy_kwh">%1$.1f kWh</string>
|
||||||
|
<string name="energy_trip_note">net, after recuperation</string>
|
||||||
|
<string name="energy_trip_waiting">once the car is on</string>
|
||||||
<string name="vehicle_row_usage_average">Usage, 5-min average</string>
|
<string name="vehicle_row_usage_average">Usage, 5-min average</string>
|
||||||
<string name="range_consumption">%1$.1f kWh/100 km</string>
|
<string name="range_consumption">%1$.1f kWh/100 km</string>
|
||||||
<string name="range_over">> %1$d km</string>
|
<string name="range_over">> %1$d km</string>
|
||||||
|
|||||||
@@ -115,4 +115,17 @@ public class RangeEstimatorTest {
|
|||||||
assertEquals(-6.25, regen.kwhPer100Km, 0.01);
|
assertEquals(-6.25, regen.kwhPer100Km, 0.01);
|
||||||
assertTrue(Double.isInfinite(regen.rangeKm));
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user