App 2.22.2: gear badge shows P, R, N and D

Codes read on the owner's car with 2.22.1: 1 P, 2 R, 3 N, 4 D. Gear
maps them (unit-tested); other codes show no letter, unread "—". The
temporary gear list on the Logs tab is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-24 19:10:03 +02:00
co-authored by Claude Opus 5.5
parent 16cc2746ef
commit c6643e7df2
12 changed files with 33 additions and 94 deletions
+6 -9
View File
@@ -1,23 +1,20 @@
package com.evsuite.abrp;
/**
* The gear letter for the dashboards, from the car's raw gear code.
*
* Only code 1 is known to be P (EVHardware, two head-unit apps and the reversing camera
* agree). The codes for R, N and D are not established yet, so any other code shows no letter
* rather than a guessed "D": the Logs tab lists the codes the car reports, for the driver to
* match them to the lever. Unread is "—".
* The gear letter for the dashboards, from the car's raw gear code: 1 P, 2 R, 3 N, 4 D, read
* on the owner's car (firmware SWI68-29958-1300R69) by shifting through the lever. Any other
* code shows no letter rather than a guess; unread is "—".
*/
final class Gear {
static final int PARK = 1;
private static final String[] LETTERS = { null, "P", "R", "N", "D" };
private Gear() {}
/** "P", "" for a gear not yet named, "—" when unread. */
/** "P", "R", "N" or "D"; "" for an unknown code, "—" when unread. */
static String label(Integer raw) {
if (raw == null) return "—";
if (raw == PARK) return "P";
if (raw > 0 && raw < LETTERS.length) return LETTERS[raw];
return "";
}
}
@@ -1,63 +0,0 @@
package com.evsuite.abrp;
import android.content.Context;
import android.content.SharedPreferences;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
/**
* Every change of the car's raw gear code with the time and speed, the last {@link #MAX_LINES},
* shown on the Logs tab. It is how the codes for R, N and D get matched to the lever: shift
* through them and read which code each one gave. Kept in preferences so the list survives
* the app being closed in between. Written only when the code changes.
*/
final class GearLog {
static final int MAX_LINES = 12;
private static final String PREFS = "gear_log";
private static final String KEY = "lines";
/** The last code seen in this process; MIN_VALUE before the first reading. */
private static int last = Integer.MIN_VALUE;
private GearLog() {}
/** Called with every reading; records a line when the code differs from the last one. */
static void note(Context context, Integer raw, Float speedKmh) {
int code = raw == null ? -1 : raw;
synchronized (GearLog.class) {
if (context == null || code == last) return;
last = code;
String line = new SimpleDateFormat("dd.MM. HH:mm:ss", Locale.US).format(new Date())
+ " code " + (raw == null ? "unread" : String.valueOf(raw))
+ (raw != null && raw == Gear.PARK ? " (P)" : "")
+ " at " + (speedKmh == null ? "—" : String.format(Locale.US, "%.0f km/h", speedKmh));
SharedPreferences prefs = context.getApplicationContext()
.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
List<String> lines = UpdateLog.append(split(prefs.getString(KEY, null)), line, MAX_LINES);
prefs.edit().putString(KEY, String.join("\n", lines)).apply();
}
}
/** The code now, null when unread or not read yet. */
static synchronized Integer current() {
return last < 0 ? null : last;
}
/** Newest first. */
static List<String> lines(Context context) {
SharedPreferences prefs = context.getApplicationContext()
.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
return split(prefs.getString(KEY, null));
}
private static List<String> split(String stored) {
if (stored == null || stored.isEmpty()) return new ArrayList<>();
return new ArrayList<>(Arrays.asList(stored.split("\n")));
}
}
@@ -1575,7 +1575,6 @@ public class MainActivity extends AppCompatActivity {
vehTireFr.setText(unit.format(s.getTirePressures().getFrontRightKpa()));
vehTireRl.setText(unit.format(s.getTirePressures().getRearLeftKpa()));
vehTireRr.setText(unit.format(s.getTirePressures().getRearRightKpa()));
GearLog.note(this, s.getGearRaw(), s.getSpeedKmh());
showDashboard(s);
showCockpit(s);
} catch (Throwable ignored) {
@@ -1900,14 +1899,6 @@ public class MainActivity extends AppCompatActivity {
// Update steps first: after a failed install, they are what the driver came here for.
java.util.List<String> updates = UpdateLog.lines(this);
StringBuilder sb = new StringBuilder();
// The car's gear codes, for matching R, N and D to the lever (only P = 1 is known).
java.util.List<String> gears = GearLog.lines(this);
if (!gears.isEmpty()) {
Integer now = GearLog.current();
sb.append(getString(R.string.gear_log_title, now == null ? "—" : String.valueOf(now)));
for (String line : gears) sb.append('\n').append(line);
sb.append("\n\n");
}
if (!updates.isEmpty()) {
sb.append(getString(R.string.update_log_title));
for (String line : updates) sb.append('\n').append(line);
-1
View File
@@ -187,7 +187,6 @@
<string name="label_call_log">Recent uploads</string>
<string name="call_log_empty">No upload attempted yet.</string>
<string name="update_log_title">Updates, newest first:</string>
<string name="gear_log_title">Gear codes, newest first (now: %1$s; only 1 = P is known):</string>
<string name="btn_flush_now">Upload now</string>
<string name="status_starting">Starting…</string>
<string name="status_running_no_upload">Running — waiting for first upload</string>
@@ -8,11 +8,18 @@ import org.junit.Test;
public class GearTest {
@Test
public void onlyParkIsNamed() {
public void theFourCodesReadOnTheCar() {
assertEquals("P", Gear.label(1));
assertEquals("", Gear.label(2));
assertEquals("", Gear.label(3));
assertEquals("", Gear.label(4));
assertEquals("R", Gear.label(2));
assertEquals("N", Gear.label(3));
assertEquals("D", Gear.label(4));
}
@Test
public void unknownCodesShowNoLetter() {
assertEquals("", Gear.label(0));
assertEquals("", Gear.label(5));
assertEquals("", Gear.label(-1));
}
@Test