App 2.40.3: the signal recorder listens to the assistance properties
The ACC recording of 2.32.0 only polled, and the AAD_* properties read their defaults. The head unit's own assistance manager never polls them: it subscribes to each for change events, and a vendor HAL may fill a value only while someone listens. EVHardware gains a read-only listenToProperties and AssistBlock (global vendor properties 0x21x0A1xx, all 108 AAD_* ids); the recorder subscribes to that block for its three minutes and lists the properties heard, those with changing values first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -7,6 +7,8 @@ import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.evsuite.hardware.AssistBlock;
|
||||
import com.evsuite.hardware.EVHardware;
|
||||
import com.evsuite.hardware.probe.VendorSurfaceProbe;
|
||||
|
||||
import java.io.File;
|
||||
@@ -26,7 +28,11 @@ import java.util.Map;
|
||||
* 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 a {@link SignalRecording}. The driver notes the times they do
|
||||
* the head unit reports, into a {@link SignalRecording}. It also listens, as the head unit's own
|
||||
* driver-assistance manager does, to the properties of the assistance block
|
||||
* ({@link AssistBlock}): subscribed for change events the whole time, because a value that is
|
||||
* only filled while someone listens reads as its default when polled (2.40.3, still the
|
||||
* cruise control). 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.)
|
||||
*
|
||||
@@ -85,6 +91,21 @@ final class SignalRecorder {
|
||||
} catch (RuntimeException ignored) {
|
||||
// No key reports: the properties are recorded all the same.
|
||||
}
|
||||
EVHardware.PropertyListening listening = null;
|
||||
try {
|
||||
listening = EVHardware.INSTANCE.listenToProperties(AssistBlock.INSTANCE::contains,
|
||||
(id, area, value) -> {
|
||||
rec.event(System.currentTimeMillis(),
|
||||
String.format(Locale.US, "CPM 0x%08X/%d", id, area), value);
|
||||
return kotlin.Unit.INSTANCE;
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
rec.note(System.currentTimeMillis(), "listening failed: " + t.getClass().getSimpleName());
|
||||
}
|
||||
rec.note(System.currentTimeMillis(), listening == null
|
||||
? "listening to the assistance properties: not possible (no car service)"
|
||||
: "listening to " + listening.getSubscribed() + " assistance properties ("
|
||||
+ listening.getRefused() + " refused)");
|
||||
try {
|
||||
long end = startedMs + DURATION_MS;
|
||||
while (running && System.currentTimeMillis() < end) {
|
||||
@@ -99,6 +120,13 @@ final class SignalRecorder {
|
||||
} catch (Throwable t) {
|
||||
rec.note(System.currentTimeMillis(), "recorder stopped: " + t.getClass().getSimpleName());
|
||||
} finally {
|
||||
if (listening != null) {
|
||||
try {
|
||||
listening.close();
|
||||
} catch (Throwable ignored) {
|
||||
// the car service is gone
|
||||
}
|
||||
}
|
||||
if (keysRegistered) {
|
||||
try {
|
||||
app.unregisterReceiver(keys);
|
||||
@@ -130,7 +158,10 @@ final class SignalRecorder {
|
||||
text.add("Steering-wheel keys and notes:");
|
||||
text.addAll(rec.notes());
|
||||
text.add("");
|
||||
text.add("Changed values, the rarest first:");
|
||||
text.add("Assistance properties heard (" + rec.events() + " change events), changing ones first:");
|
||||
text.addAll(rec.heard());
|
||||
text.add("");
|
||||
text.add("Changed values (polled), the rarest first:");
|
||||
text.addAll(rec.summary());
|
||||
text.add("");
|
||||
text.add("Everything, in order:");
|
||||
@@ -168,14 +199,17 @@ final class SignalRecorder {
|
||||
long elapsed = (running ? System.currentTimeMillis() : startedMs + DURATION_MS) - startedMs;
|
||||
if (running) {
|
||||
out.add(context.getString(R.string.signal_recorder_running, elapsed / 60_000, (elapsed / 1000) % 60,
|
||||
recording.captures()));
|
||||
recording.captures(), recording.events()));
|
||||
} else {
|
||||
out.add(context.getString(R.string.signal_recorder_done, SignalRecording.time(startedMs),
|
||||
recording.captures(), result == null ? "…" : result));
|
||||
recording.captures(), recording.events(), result == null ? "…" : result));
|
||||
}
|
||||
// Steering-wheel keys and the like first: few, and each one a lead.
|
||||
List<String> notes = recording.notes();
|
||||
out.addAll(notes.subList(Math.max(0, notes.size() - 10), notes.size()));
|
||||
// Then what the listening heard, changing properties first.
|
||||
List<String> heard = recording.heard();
|
||||
for (String line : heard.subList(0, Math.min(12, heard.size()))) out.add("heard " + line);
|
||||
out.addAll(recording.summary());
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ import java.util.TreeSet;
|
||||
* 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.
|
||||
*
|
||||
* Besides the captures (polled), it keeps the change events of the properties the recorder
|
||||
* listens to ({@link #event}): how many per property and through which values. A value the
|
||||
* head unit only fills while someone listens shows up there and nowhere else.
|
||||
* Pure Java, unit-tested.
|
||||
*/
|
||||
final class SignalRecording {
|
||||
@@ -31,6 +35,10 @@ final class SignalRecording {
|
||||
private final List<String> notes = new ArrayList<>();
|
||||
private final Map<String, Integer> changes = new HashMap<>();
|
||||
private final Map<String, LinkedHashSet<String>> seen = new HashMap<>();
|
||||
private int events;
|
||||
private final Map<String, String> lastEvent = new HashMap<>();
|
||||
private final Map<String, Integer> eventCounts = new HashMap<>();
|
||||
private final Map<String, LinkedHashSet<String>> eventValues = new HashMap<>();
|
||||
|
||||
static String time(long ms) {
|
||||
return new SimpleDateFormat("HH:mm:ss", Locale.US).format(new Date(ms));
|
||||
@@ -65,6 +73,45 @@ final class SignalRecording {
|
||||
notes.add(time(ms) + " " + what);
|
||||
}
|
||||
|
||||
/**
|
||||
* A change event from a property listened to. Every one is counted; the timeline gets a
|
||||
* line only when the value differs from that property's last event.
|
||||
*/
|
||||
synchronized void event(long ms, String key, String value) {
|
||||
events++;
|
||||
eventCounts.merge(key, 1, Integer::sum);
|
||||
String before = lastEvent.put(key, value);
|
||||
if (value.equals(before)) return;
|
||||
lines.add(time(ms) + " heard " + key + " = " + value);
|
||||
LinkedHashSet<String> values = eventValues.computeIfAbsent(key, k -> new LinkedHashSet<>());
|
||||
if (values.size() < VALUES_KEPT) values.add(shorten(value));
|
||||
}
|
||||
|
||||
synchronized int events() {
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every property that sent events: first those that sent different values, the fewest
|
||||
* events first (the leads), then those that only ever repeated one value.
|
||||
*/
|
||||
synchronized List<String> heard() {
|
||||
List<String> keys = new ArrayList<>(eventCounts.keySet());
|
||||
keys.sort((a, b) -> {
|
||||
boolean va = eventValues.get(a).size() > 1, vb = eventValues.get(b).size() > 1;
|
||||
if (va != vb) return va ? -1 : 1;
|
||||
int c = Integer.compare(eventCounts.get(a), eventCounts.get(b));
|
||||
return c != 0 ? c : a.compareTo(b);
|
||||
});
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String key : keys) {
|
||||
LinkedHashSet<String> values = eventValues.get(key);
|
||||
out.add(key + " " + eventCounts.get(key) + " events " + String.join(" → ", values)
|
||||
+ (values.size() >= VALUES_KEPT ? " …" : ""));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The notes alone, in order. */
|
||||
synchronized List<String> notes() {
|
||||
return new ArrayList<>(notes);
|
||||
|
||||
@@ -223,11 +223,11 @@
|
||||
<string name="drive_mode_unreadable">Not changed: speed or gear unreadable.</string>
|
||||
<string name="drive_mode_unknown">an unknown mode (%1$s)</string>
|
||||
<string name="label_signal_recorder">Signal recorder</string>
|
||||
<string name="signal_recorder_hint">Records every value the head unit offers for 3 minutes, plus the steering-wheel keys it reports, and lists what changed, the rarest first. To find a signal: start it, note the times you switch the feature on and off or change it, and look for the values that changed then. With a USB stick plugged in, the whole recording is saved on it too (Android/data/…/files).</string>
|
||||
<string name="signal_recorder_hint">Records every value the head unit offers for 3 minutes, listens to the driver-assistance properties for every change they report, and notes the steering-wheel keys; then lists what changed, the rarest first. To find a signal: start it, note the times you switch the feature on and off or change it, and look for the values that changed then. With a USB stick plugged in, the whole recording is saved on it too (Android/data/…/files).</string>
|
||||
<string name="btn_signal_record">Record 3 minutes</string>
|
||||
<string name="btn_signal_stop">Stop recording</string>
|
||||
<string name="signal_recorder_running">Recording: %1$d:%2$02d of 3:00, %3$d captures. Keys reported, then values changed so far, the rarest first:</string>
|
||||
<string name="signal_recorder_done">Recording from %1$s: %2$d captures, %3$s. Keys reported, then values changed, the rarest first:</string>
|
||||
<string name="signal_recorder_running">Recording: %1$d:%2$02d of 3:00, %3$d captures, %4$d change events heard. Keys reported, then assistance properties heard, then values changed so far, the rarest first:</string>
|
||||
<string name="signal_recorder_done">Recording from %1$s: %2$d captures, %3$d change events heard, %4$s. Keys reported, then assistance properties heard, then values changed, the rarest first:</string>
|
||||
<string name="call_log_empty">No upload attempted yet.</string>
|
||||
<string name="update_log_title">Updates, newest first:</string>
|
||||
<string name="btn_flush_now">Upload now</string>
|
||||
|
||||
Reference in New Issue
Block a user