App 2.13.0: tyre pressures in bar; 5-minute average power

Tyre pressures show in bar by default; Settings → Display offers kPa
and psi as well. Display only: the car reports kPa and the server keeps
storing kPa.

A "Power, 5-min average" card under Power: the time-weighted mean
battery power over the last five minutes, standing included, from the
same samples as the 5-minute range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-18 13:26:02 +02:00
co-authored by Claude Opus 5.5
parent 66fed48aae
commit 40906ec4a7
12 changed files with 159 additions and 8 deletions
+3
View File
@@ -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.13.0: tyre pressures in bar by default, kPa or psi selectable (Settings →
Display; display only, the car and the server keep kPa). "Power, 5-min average" card under
Power: time-weighted mean battery power, standing included.
- 2026-10-18 — App 2.12.1: no scroll bars (the 2-second refresh kept them from ever fading); - 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 "Energy since start" tile (net kWh of the trip, recuperation counted back) left of "Usage since
start". start".
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4" applicationId "com.stinnesbeck.nilsmg4"
minSdk 27 minSdk 27
targetSdk 36 targetSdk 36
versionCode 76 versionCode 77
versionName "2.12.1" versionName "2.13.0"
// 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.
@@ -93,7 +93,7 @@ public class MainActivity extends AppCompatActivity {
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; vehEnergyTrip, vehEnergyTripNote, vehPowerAverage, vehPowerAverageNote;
/** 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";
@@ -977,6 +977,7 @@ public class MainActivity extends AppCompatActivity {
voiceKeySwitch.setChecked(UploadSettings.voiceKeyOpensApp(prefs)); voiceKeySwitch.setChecked(UploadSettings.voiceKeyOpensApp(prefs));
voiceKeySwitch.setOnCheckedChangeListener((btn, checked) -> voiceKeySwitch.setOnCheckedChangeListener((btn, checked) ->
prefs.edit().putBoolean(UploadSettings.KEY_VOICE_KEY_OPENS, checked).apply()); prefs.edit().putBoolean(UploadSettings.KEY_VOICE_KEY_OPENS, checked).apply());
bindTireUnit();
fullScreenSwitch = settingsPane.findViewById(R.id.full_screen_switch); fullScreenSwitch = settingsPane.findViewById(R.id.full_screen_switch);
fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs)); fullScreenSwitch.setChecked(UploadSettings.fullScreen(prefs));
fullScreenSwitch.setOnCheckedChangeListener((btn, checked) -> { fullScreenSwitch.setOnCheckedChangeListener((btn, checked) -> {
@@ -1057,6 +1058,26 @@ public class MainActivity extends AppCompatActivity {
}, "abrp-test").start(); }, "abrp-test").start();
} }
/** Tyre pressure unit: display only, read on every refresh of the Overview. */
private void bindTireUnit() {
android.widget.Spinner spinner = settingsPane.findViewById(R.id.tire_unit_spinner);
PressureUnit[] units = PressureUnit.values();
String[] labels = new String[units.length];
for (int i = 0; i < units.length; i++) labels[i] = units[i].label;
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(
this, R.layout.spinner_value, labels);
adapter.setDropDownViewResource(R.layout.spinner_dropdown);
spinner.setAdapter(adapter);
spinner.setSelection(PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null)).ordinal());
spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
prefs.edit().putString(PressureUnit.PREF_KEY, units[position].name()).apply();
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) { }
});
}
/** The service reads these settings on every tick, so a change applies without a restart. */ /** The service reads these settings on every tick, so a change applies without a restart. */
private void bindCadenceControls() { private void bindCadenceControls() {
UploadSettings current = UploadSettings.from(prefs); UploadSettings current = UploadSettings.from(prefs);
@@ -1190,6 +1211,8 @@ public class MainActivity extends AppCompatActivity {
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); vehEnergyTrip = vehiclePane.findViewById(R.id.vehicle_energy_trip);
vehEnergyTripNote = vehiclePane.findViewById(R.id.vehicle_energy_trip_note); vehEnergyTripNote = vehiclePane.findViewById(R.id.vehicle_energy_trip_note);
vehPowerAverage = vehiclePane.findViewById(R.id.vehicle_power_average);
vehPowerAverageNote = vehiclePane.findViewById(R.id.vehicle_power_average_note);
} }
private double usableKwh() { private double usableKwh() {
@@ -1207,6 +1230,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 power = estimator.averagePowerKw(monotonicMs);
vehPowerAverage.setText(power == null ? "—" : getString(R.string.power_kw, power));
vehPowerAverageNote.setText(power == null ? R.string.power_waiting : R.string.usage_last_5_min);
Double energy = estimator.energySinceStart(); Double energy = estimator.energySinceStart();
vehEnergyTrip.setText(energy == null ? "—" : getString(R.string.energy_kwh, energy)); vehEnergyTrip.setText(energy == null ? "—" : getString(R.string.energy_kwh, energy));
vehEnergyTripNote.setText(energy == null ? R.string.energy_trip_waiting : R.string.energy_trip_note); vehEnergyTripNote.setText(energy == null ? R.string.energy_trip_waiting : R.string.energy_trip_note);
@@ -1264,10 +1290,11 @@ public class MainActivity extends AppCompatActivity {
vehCharging.setText(chargingLabel(s)); vehCharging.setText(chargingLabel(s));
refreshRangeEstimates(s); refreshRangeEstimates(s);
vehTireFl.setText(fmt(s.getTirePressures().getFrontLeftKpa(), "%.0f kPa")); PressureUnit unit = PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null));
vehTireFr.setText(fmt(s.getTirePressures().getFrontRightKpa(), "%.0f kPa")); vehTireFl.setText(unit.format(s.getTirePressures().getFrontLeftKpa()));
vehTireRl.setText(fmt(s.getTirePressures().getRearLeftKpa(), "%.0f kPa")); vehTireFr.setText(unit.format(s.getTirePressures().getFrontRightKpa()));
vehTireRr.setText(fmt(s.getTirePressures().getRearRightKpa(), "%.0f kPa")); vehTireRl.setText(unit.format(s.getTirePressures().getRearLeftKpa()));
vehTireRr.setText(unit.format(s.getTirePressures().getRearRightKpa()));
} catch (Throwable ignored) { } catch (Throwable ignored) {
// Leave the last good reading on screen. // Leave the last good reading on screen.
} }
@@ -0,0 +1,40 @@
package com.evsuite.abrp;
import java.util.Locale;
/**
* The unit tyre pressures are shown in. The car reports kPa; this only changes the display.
* Android-free, unit-tested.
*/
enum PressureUnit {
BAR("bar", "%.1f bar", 0.01),
KPA("kPa", "%.0f kPa", 1.0),
PSI("psi", "%.1f psi", 0.145038);
static final String PREF_KEY = "tire_pressure_unit";
static final PressureUnit DEFAULT = BAR;
final String label;
private final String pattern;
private final double perKpa;
PressureUnit(String label, String pattern, double perKpa) {
this.label = label;
this.pattern = pattern;
this.perKpa = perKpa;
}
/** A reading in this unit, or a dash for none. */
String format(Float kpa) {
if (kpa == null || kpa.isNaN() || kpa.isInfinite()) return "—";
return String.format(Locale.US, pattern, kpa * perKpa);
}
/** The stored choice; anything unknown falls back to bar. */
static PressureUnit fromPref(String name) {
for (PressureUnit u : values()) {
if (u.name().equals(name)) return u;
}
return DEFAULT;
}
}
@@ -154,6 +154,29 @@ final class RangeEstimator {
return new Estimate(rangeKm, kwhPerKm * 100, distanceKm); return new Estimate(rangeKm, kwhPerKm * 100, distanceKm);
} }
/**
* Mean battery power over the last five minutes, time-weighted, standing included; null with
* less than a minute of samples. Positive is drawn from the pack, as the Power tile shows it.
*/
synchronized Double averagePowerKw(long nowMs) {
double energyKwh = 0;
long covered = 0;
Sample previous = null;
for (Sample s : samples) {
if (s.timeMs < nowMs - WINDOW_MS) continue;
if (previous != null) {
long dt = s.timeMs - previous.timeMs;
if (dt > 0 && dt <= MAX_GAP_MS) {
energyKwh += (previous.powerKw + s.powerKw) / 2 * (dt / 3_600_000.0);
covered += dt;
}
}
previous = s;
}
if (covered < MIN_WINDOW_MS) return null;
return energyKwh / (covered / 3_600_000.0);
}
synchronized void clear() { synchronized void clear() {
samples.clear(); samples.clear();
tripEnergyKwh = 0; tripEnergyKwh = 0;
@@ -8,6 +8,8 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" /> <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<Spinner android:id="@+id/tire_unit_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:layout_marginTop="12dp" android:background="@drawable/bg_input_panel" android:popupBackground="@color/ev_surface_raised" android:contentDescription="@string/label_tire_unit" android:spinnerMode="dropdown" />
</LinearLayout> </LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_settings_title" android:textAppearance="@style/Text.EV.Eyebrow" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_settings_title" android:textAppearance="@style/Text.EV.Eyebrow" />
@@ -47,7 +47,11 @@
<TextView android:id="@+id/vehicle_usage_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_usage_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_usage_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_usage_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>
<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_power_average" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:id="@+id/vehicle_power_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_power_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>
<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> </LinearLayout>
<Space android:layout_width="1dp" android:layout_height="16dp" /> <Space android:layout_width="1dp" android:layout_height="16dp" />
@@ -7,6 +7,8 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_full_screen" android:textAppearance="@style/Text.EV.Secondary" />
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" /> <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/voice_key_switch" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:text="@string/label_voice_key" android:textAppearance="@style/Text.EV.Body" android:paddingTop="8dp" android:paddingBottom="8dp" app:useMaterialThemeColors="true" app:thumbTint="@color/switch_thumb" app:trackTint="@color/switch_track" android:textColor="@color/ev_text_primary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hint_voice_key" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/label_tire_unit" android:textAppearance="@style/Text.EV.Body" android:layout_marginTop="20dp" />
<Spinner android:id="@+id/tire_unit_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="72dp" android:layout_marginTop="12dp" android:background="@drawable/bg_input_panel" android:popupBackground="@color/ev_surface_raised" android:contentDescription="@string/label_tire_unit" android:spinnerMode="dropdown" />
</LinearLayout> </LinearLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="24dp" android:background="@drawable/bg_glass" android:layout_marginTop="16dp">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_settings_title" android:textAppearance="@style/Text.EV.Eyebrow" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/design_battery_settings_title" android:textAppearance="@style/Text.EV.Eyebrow" />
+5
View File
@@ -40,6 +40,11 @@
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_power" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:id="@+id/vehicle_power" 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_power" 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" />
</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_power_average" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:id="@+id/vehicle_power_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_power_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 android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp"> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="112dp" android:padding="16dp" android:background="@drawable/bg_glass" android:layout_columnWeight="1" android:layout_margin="4dp">
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/vehicle_row_speed" android:textAppearance="@style/Text.EV.Secondary" />
<TextView android:id="@+id/vehicle_speed" 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_speed" 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" />
+4
View File
@@ -35,6 +35,10 @@
<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="vehicle_row_energy_trip">Energy since start</string>
<string name="vehicle_row_power_average">Power, 5-min average</string>
<string name="power_kw">%1$.1f kW</string>
<string name="power_waiting">needs a minute of data</string>
<string name="label_tire_unit">Tyre pressure unit</string>
<string name="energy_kwh">%1$.1f kWh</string> <string name="energy_kwh">%1$.1f kWh</string>
<string name="energy_trip_note">net, after recuperation</string> <string name="energy_trip_note">net, after recuperation</string>
<string name="energy_trip_waiting">once the car is on</string> <string name="energy_trip_waiting">once the car is on</string>
@@ -0,0 +1,29 @@
package com.evsuite.abrp;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/** Tyre pressure display units — pure JVM. */
public class PressureUnitTest {
@Test
public void kpaFromTheCarInEachUnit() {
assertEquals("2.5 bar", PressureUnit.BAR.format(250f));
assertEquals("250 kPa", PressureUnit.KPA.format(250f));
assertEquals("36.3 psi", PressureUnit.PSI.format(250f));
}
@Test
public void anUnreadPressureStaysADash() {
assertEquals("—", PressureUnit.BAR.format(null));
assertEquals("—", PressureUnit.PSI.format(Float.NaN));
}
@Test
public void barUnlessSomethingElseWasChosen() {
assertEquals(PressureUnit.BAR, PressureUnit.fromPref(null));
assertEquals(PressureUnit.BAR, PressureUnit.fromPref("nonsense"));
assertEquals(PressureUnit.PSI, PressureUnit.fromPref("PSI"));
}
}
@@ -128,4 +128,16 @@ public class RangeEstimatorTest {
for (long t = 615_000; t <= 735_000; t += 15_000) r.add(t, -20f, 60f); // downhill 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 assertTrue(r.energySinceStart() < 5.0); // recuperation counts back
} }
@Test
public void averagePowerOverTheLastFiveMinutes() {
RangeEstimator r = new RangeEstimator();
r.add(0, 20f, 50f);
assertNull(r.averagePowerKw(0)); // too little yet
for (long t = 15_000; t <= 300_000; t += 15_000) r.add(t, 20f, 50f);
assertEquals(20.0, r.averagePowerKw(300_000), 1e-6);
// Standing with the climate on counts: half the window at 2 kW.
for (long t = 315_000; t <= 450_000; t += 15_000) r.add(t, 2f, 0f);
assertEquals(11.0, r.averagePowerKw(450_000), 1.0);
}
} }