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:
@@ -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
@@ -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" />
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user