App 2.32.1: drop the cruise control reads; the recorder becomes general
The 2.32.0 recording (ACC set to 101, 90 and 80 km/h behind another car) found none of it among 1,194 values: the head unit does not get the ACC data. Removed the cruise log and the set-speed read (EVHardware is as before 2.27.3); the finding is under "Signals the car does not provide". The recorder stays as the Signal recorder, for finding other signals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -1,153 +0,0 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The adaptive cruise control debug log on the Logs tab: the driver-assistance properties the
|
||||
* car reports (`EVHardware.readAccDebug`, raw: the cruise control target speed, the assist
|
||||
* modes, the object the ACC follows) with the time, speed and gear. It is how they get matched
|
||||
* to what the cluster shows: drive with the app open, set the cruise control, change the speed
|
||||
* and the distance, switch it off, and read what the car reported.
|
||||
*
|
||||
* A line is written at once when a mode or the target speed changes, and every
|
||||
* {@link #DRIVING_EVERY_MS} while driving (the followed object's values change all the time,
|
||||
* so they alone never write one). The last {@link #MAX_LINES} lines are kept in a file in the
|
||||
* app's own storage, appended to, so a drive does not rewrite it every few seconds.
|
||||
*/
|
||||
final class CruiseLog {
|
||||
|
||||
/** About 20 minutes of driving at one line per two seconds. */
|
||||
static final int MAX_LINES = 600;
|
||||
/** The newest lines the Logs tab shows. */
|
||||
static final int SHOWN_LINES = 150;
|
||||
static final long DRIVING_EVERY_MS = 2000;
|
||||
/** Values that change all the time while following a car: they never write a line alone. */
|
||||
private static final List<String> MOVING = java.util.Arrays.asList("obj", "dist", "rel");
|
||||
private static final String FILE = "cruise_log.txt";
|
||||
|
||||
private static ArrayDeque<String> lines;
|
||||
private static String lastSteady;
|
||||
private static long lastWriteMs;
|
||||
private static String current = "—";
|
||||
|
||||
private CruiseLog() {}
|
||||
|
||||
/** The values that write a line when they change: everything but the followed object. */
|
||||
static String steady(List<String[]> readings) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String[] r : readings) {
|
||||
if (!MOVING.contains(r[0])) sb.append(r[0]).append(' ').append(r[1]).append(" ");
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/** All readings as the log writes them. */
|
||||
static String values(List<String[]> readings) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String[] r : readings) sb.append(r[0]).append(' ').append(r[1]).append(" ");
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/** Whether this reading gets a line: a changed mode or target, or time while driving. */
|
||||
static boolean due(String steady, String lastSteady, long nowMs, long lastWriteMs, boolean driving) {
|
||||
if (!steady.equals(lastSteady)) return true;
|
||||
return driving && nowMs - lastWriteMs >= DRIVING_EVERY_MS;
|
||||
}
|
||||
|
||||
/** Driving: in D or R, or moving. Unread values are not driving. */
|
||||
static boolean driving(Float speedKmh, Integer gear) {
|
||||
return (gear != null && (gear == 4 || gear == 2)) || (speedKmh != null && speedKmh > 0.5f);
|
||||
}
|
||||
|
||||
/** Called with each reading, off the main thread. */
|
||||
static void note(Context context, List<String[]> readings, Float speedKmh, Integer gear) {
|
||||
if (context == null || readings == null || readings.isEmpty()) return;
|
||||
long now = System.currentTimeMillis();
|
||||
String steady = steady(readings);
|
||||
synchronized (CruiseLog.class) {
|
||||
current = values(readings);
|
||||
if (!due(steady, lastSteady, now, lastWriteMs, driving(speedKmh, gear))) return;
|
||||
lastSteady = steady;
|
||||
lastWriteMs = now;
|
||||
String label = Gear.label(gear);
|
||||
String line = new SimpleDateFormat("dd.MM. HH:mm:ss", Locale.US).format(new Date(now))
|
||||
+ " " + (speedKmh == null ? "—" : String.format(Locale.US, "%.0f km/h", speedKmh))
|
||||
+ " " + (label.isEmpty() ? "?" : label)
|
||||
+ " " + current;
|
||||
load(context);
|
||||
lines.addLast(line);
|
||||
File file = file(context);
|
||||
if (lines.size() > MAX_LINES + 100) {
|
||||
// Trim now and then: the file is otherwise only appended to.
|
||||
while (lines.size() > MAX_LINES) lines.removeFirst();
|
||||
write(file, lines, false);
|
||||
} else {
|
||||
List<String> one = new ArrayList<>();
|
||||
one.add(line);
|
||||
write(file, one, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** All readings now, "—" before the first. */
|
||||
static synchronized String current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
/** The newest {@link #SHOWN_LINES}, newest first. */
|
||||
static synchronized List<String> lines(Context context) {
|
||||
load(context);
|
||||
List<String> newest = new ArrayList<>();
|
||||
Iterator<String> it = lines.descendingIterator();
|
||||
while (it.hasNext() && newest.size() < SHOWN_LINES) newest.add(it.next());
|
||||
return newest;
|
||||
}
|
||||
|
||||
private static File file(Context context) {
|
||||
return new File(context.getApplicationContext().getFilesDir(), FILE);
|
||||
}
|
||||
|
||||
/** Reads the file once per process; drops the change-only log of 2.27.3. */
|
||||
private static void load(Context context) {
|
||||
if (lines != null) return;
|
||||
lines = new ArrayDeque<>();
|
||||
context.getApplicationContext().deleteSharedPreferences("cruise_log");
|
||||
File file = file(context);
|
||||
if (!file.exists()) return;
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(file), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
if (line.isEmpty()) continue;
|
||||
lines.addLast(line);
|
||||
if (lines.size() > MAX_LINES) lines.removeFirst();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// An unreadable log starts again empty.
|
||||
}
|
||||
}
|
||||
|
||||
private static void write(File file, Iterable<String> text, boolean append) {
|
||||
try (Writer out = new OutputStreamWriter(new FileOutputStream(file, append), StandardCharsets.UTF_8)) {
|
||||
for (String line : text) out.write(line + "\n");
|
||||
} catch (Exception ignored) {
|
||||
// A debug log that cannot be written is not worth disturbing the screen for.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +363,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
settingsPane, logPane };
|
||||
|
||||
UpdateLog.noteVersion(this, BuildConfig.VERSION_NAME);
|
||||
// The cruise control logs of 2.31.1–2.32.0, gone with the feature (SPECS.md).
|
||||
deleteFile("cruise_log.txt");
|
||||
deleteFile("acc_recording.txt");
|
||||
bindVehiclePane();
|
||||
bindGauges();
|
||||
bindCockpit();
|
||||
@@ -375,10 +378,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
statusText = servicePane.findViewById(R.id.status_text);
|
||||
callLogText = logPane.findViewById(R.id.call_log_text);
|
||||
logPane.findViewById(R.id.flush_now_button).setOnClickListener(v -> manualFlush());
|
||||
logPane.findViewById(R.id.acc_record_button).setOnClickListener(v -> {
|
||||
if (AccRecorder.running()) AccRecorder.stop();
|
||||
else AccRecorder.start(this);
|
||||
refreshAccRecorder();
|
||||
logPane.findViewById(R.id.signal_record_button).setOnClickListener(v -> {
|
||||
if (SignalRecorder.running()) SignalRecorder.stop();
|
||||
else SignalRecorder.start(this);
|
||||
refreshSignalRecorder();
|
||||
});
|
||||
|
||||
setUpPager();
|
||||
@@ -1508,9 +1511,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
* About twenty calls into the car's services: on a background thread, so the screen never
|
||||
* waits for them; the values are shown on the main thread once they are in.
|
||||
*/
|
||||
/** Counts readings on the reader thread, for the once-a-second cruise control log. */
|
||||
private int accDebugTick;
|
||||
|
||||
private void readVehicle() {
|
||||
if (!reading.compareAndSet(false, true)) return;
|
||||
try {
|
||||
@@ -1522,14 +1522,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
// The timestamp is passed explicitly: its Kotlin default is not visible from Java.
|
||||
EnergySnapshot s = vehicleReader.read(System.currentTimeMillis());
|
||||
latestSnapshot = s;
|
||||
// The cruise control debug log, once a second: about eight more calls.
|
||||
if (accDebugTick++ % 5 == 0) {
|
||||
java.util.List<String[]> acc = new java.util.ArrayList<>();
|
||||
for (kotlin.Pair<String, String> r : com.evsuite.hardware.EVHardware.INSTANCE.readAccDebug()) {
|
||||
acc.add(new String[] { r.getFirst(), r.getSecond() });
|
||||
}
|
||||
CruiseLog.note(getApplicationContext(), acc, s.getSpeedKmh(), s.getGearRaw());
|
||||
}
|
||||
runOnUiThread(() -> {
|
||||
if (!isDestroyed()) showVehicle(s);
|
||||
});
|
||||
@@ -2134,11 +2126,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
/** The ACC recorder's button and what it has found so far. */
|
||||
private void refreshAccRecorder() {
|
||||
MaterialButton button = logPane.findViewById(R.id.acc_record_button);
|
||||
button.setText(AccRecorder.running() ? R.string.btn_acc_stop : R.string.btn_acc_record);
|
||||
TextView text = logPane.findViewById(R.id.acc_record_text);
|
||||
java.util.List<String> status = AccRecorder.status(this);
|
||||
private void refreshSignalRecorder() {
|
||||
MaterialButton button = logPane.findViewById(R.id.signal_record_button);
|
||||
button.setText(SignalRecorder.running() ? R.string.btn_signal_stop : R.string.btn_signal_record);
|
||||
TextView text = logPane.findViewById(R.id.signal_record_text);
|
||||
java.util.List<String> status = SignalRecorder.status(this);
|
||||
text.setVisibility(status.isEmpty() ? View.GONE : View.VISIBLE);
|
||||
// The rarest changes are the leads; the rest is in the saved recording.
|
||||
if (status.size() > 81) status = status.subList(0, 81);
|
||||
@@ -2146,18 +2138,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void refreshCallLog() {
|
||||
refreshAccRecorder();
|
||||
refreshSignalRecorder();
|
||||
java.util.List<UploadLog.Entry> entries = AbrpUploadService.log().recent();
|
||||
// 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 cruise control target speed as the car reports it, to match it on the car.
|
||||
java.util.List<String> cruise = CruiseLog.lines(this);
|
||||
if (!cruise.isEmpty()) {
|
||||
sb.append(getString(R.string.cruise_log_title, CruiseLog.current()));
|
||||
for (String line : cruise) 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);
|
||||
|
||||
+18
-18
@@ -23,12 +23,12 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The ACC change recorder on the Logs tab: for {@link #DURATION_MS} it reads every value the
|
||||
* The signal recorder on the Logs tab: for {@link #DURATION_MS} it reads every value the
|
||||
* head unit offers (`VendorSurfaceProbe.capture`: every CarPropertyManager property and every
|
||||
* value getter of the vendor objects, read only) again and again, and every steering-wheel key
|
||||
* the head unit reports, into an {@link AccRecording}. The driver notes the times they switch
|
||||
* the cruise control on, change the set speed and the distance, and switch it off; the values
|
||||
* that change then are the leads.
|
||||
* the head unit reports, into a {@link SignalRecording}. The driver notes the times they do
|
||||
* what is to be found; the values that change then are the leads. (Built to find the adaptive
|
||||
* cruise control, 2.32.0: on SWI68 the head unit gets none of it, see SPECS.md.)
|
||||
*
|
||||
* A full capture is far heavier than the Overview's reading, so the next one waits at least
|
||||
* {@link #MIN_INTERVAL_MS} and at least as long as the last one took: the recorder uses at most
|
||||
@@ -36,19 +36,19 @@ import java.util.Map;
|
||||
* keeps the process), and stops by itself. At the end the recording goes to the app's storage
|
||||
* and to the app's folder on every USB stick plugged in.
|
||||
*/
|
||||
final class AccRecorder {
|
||||
final class SignalRecorder {
|
||||
|
||||
static final long DURATION_MS = 3 * 60_000L;
|
||||
static final long MIN_INTERVAL_MS = 2_000L;
|
||||
static final String FILE = "acc_recording.txt";
|
||||
static final String FILE = "signal_recording.txt";
|
||||
|
||||
private static AccRecording recording;
|
||||
private static SignalRecording recording;
|
||||
private static volatile boolean running;
|
||||
private static long startedMs;
|
||||
private static String result;
|
||||
private static Thread thread;
|
||||
|
||||
private AccRecorder() {}
|
||||
private SignalRecorder() {}
|
||||
|
||||
static synchronized boolean running() {
|
||||
return running;
|
||||
@@ -57,11 +57,11 @@ final class AccRecorder {
|
||||
static synchronized void start(Context context) {
|
||||
if (running) return;
|
||||
Context app = context.getApplicationContext();
|
||||
recording = new AccRecording();
|
||||
recording = new SignalRecording();
|
||||
result = null;
|
||||
startedMs = System.currentTimeMillis();
|
||||
running = true;
|
||||
thread = new Thread(() -> record(app), "acc-recorder");
|
||||
thread = new Thread(() -> record(app), "signal-recorder");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ final class AccRecorder {
|
||||
}
|
||||
|
||||
private static void record(Context app) {
|
||||
AccRecording rec = recording;
|
||||
SignalRecording rec = recording;
|
||||
BroadcastReceiver keys = new BroadcastReceiver() {
|
||||
@Override public void onReceive(Context c, Intent intent) {
|
||||
rec.note(System.currentTimeMillis(), "steering-wheel key: " + extras(intent.getExtras()));
|
||||
@@ -107,7 +107,7 @@ final class AccRecorder {
|
||||
}
|
||||
}
|
||||
String saved = save(app, rec);
|
||||
synchronized (AccRecorder.class) {
|
||||
synchronized (SignalRecorder.class) {
|
||||
running = false;
|
||||
result = saved;
|
||||
}
|
||||
@@ -122,10 +122,10 @@ final class AccRecorder {
|
||||
}
|
||||
|
||||
/** Writes the recording; says where it went. */
|
||||
private static String save(Context app, AccRecording rec) {
|
||||
private static String save(Context app, SignalRecording rec) {
|
||||
List<String> text = new ArrayList<>();
|
||||
text.add("ACC recording, " + rec.captures() + " captures from "
|
||||
+ AccRecording.time(startedMs) + ", app " + BuildConfig.VERSION_NAME);
|
||||
text.add("Signal recording, " + rec.captures() + " captures from "
|
||||
+ SignalRecording.time(startedMs) + ", app " + BuildConfig.VERSION_NAME);
|
||||
text.add("");
|
||||
text.add("Steering-wheel keys and notes:");
|
||||
text.addAll(rec.notes());
|
||||
@@ -137,7 +137,7 @@ final class AccRecorder {
|
||||
text.addAll(rec.lines());
|
||||
List<String> places = new ArrayList<>();
|
||||
if (write(new File(app.getFilesDir(), FILE), text)) places.add("app");
|
||||
String name = "acc-recording-" + new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US)
|
||||
String name = "signal-recording-" + new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US)
|
||||
.format(new Date(startedMs)) + ".txt";
|
||||
File[] dirs = app.getExternalFilesDirs(null);
|
||||
for (File dir : dirs == null ? new File[0] : dirs) {
|
||||
@@ -167,10 +167,10 @@ final class AccRecorder {
|
||||
if (recording == null) return out;
|
||||
long elapsed = (running ? System.currentTimeMillis() : startedMs + DURATION_MS) - startedMs;
|
||||
if (running) {
|
||||
out.add(context.getString(R.string.acc_recorder_running, elapsed / 60_000, (elapsed / 1000) % 60,
|
||||
out.add(context.getString(R.string.signal_recorder_running, elapsed / 60_000, (elapsed / 1000) % 60,
|
||||
recording.captures()));
|
||||
} else {
|
||||
out.add(context.getString(R.string.acc_recorder_done, AccRecording.time(startedMs),
|
||||
out.add(context.getString(R.string.signal_recorder_done, SignalRecording.time(startedMs),
|
||||
recording.captures(), result == null ? "…" : result));
|
||||
}
|
||||
// Steering-wheel keys and the like first: few, and each one a lead.
|
||||
+8
-8
@@ -11,14 +11,14 @@ import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* One recording of the ACC change recorder ({@link AccRecorder}): every value the head unit
|
||||
* offers, captured again and again while the driver switches the adaptive cruise control on,
|
||||
* changes the set speed and the distance, and switches it off. It keeps what changed, when,
|
||||
* and per value how often and through which values, so the value that follows the cruise
|
||||
* control stands out: it changes a few times, at the moments the driver noted.
|
||||
* One recording of the signal recorder ({@link SignalRecorder}): every value the head unit
|
||||
* offers, captured again and again while the driver does what is to be found (switches a
|
||||
* feature on and off, changes a setting). It keeps what changed, when, and per value how
|
||||
* often and through which values, so the value that follows the action stands out: it
|
||||
* changes a few times, at the moments the driver noted.
|
||||
* Pure Java, unit-tested.
|
||||
*/
|
||||
final class AccRecording {
|
||||
final class SignalRecording {
|
||||
|
||||
/** Distinct values kept per key for the summary. */
|
||||
static final int VALUES_KEPT = 6;
|
||||
@@ -89,8 +89,8 @@ final class AccRecording {
|
||||
}
|
||||
|
||||
/**
|
||||
* Every value that changed, the rarest first (the cruise control's switch and set speed
|
||||
* change a handful of times; speed and power all the time): key, how often, the values.
|
||||
* Every value that changed, the rarest first (a switch the driver flips changes a handful
|
||||
* of times; speed and power all the time): key, how often, the values.
|
||||
*/
|
||||
synchronized List<String> summary() {
|
||||
List<String> keys = new ArrayList<>(changes.keySet());
|
||||
Reference in New Issue
Block a user