App 2.33.0: set the drive mode from Settings, only in P at a standstill
The owner lifted the read-only rule for the drive mode alone. Settings has Eco, Normal, Sport, Snow and Custom; the lit button is what the car reports. A tap reads gear and speed fresh and writes only in P at 0 km/h (DriveModeWrite, unit-tested; unreadable is a refusal), through a CPM-only write behind EVHardware's standstill gate, then reads the mode back and says what the car reports. The wide layout's charging price joined the battery card so the page still fits. Not tried on the car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -1177,6 +1177,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
prefs.edit().putBoolean(UploadSettings.KEY_VOICE_KEY_OPENS, checked).apply());
|
||||
bindTireUnit();
|
||||
bindMascotCharacter();
|
||||
bindDriveMode();
|
||||
bindOverviewStyle();
|
||||
bindAccentColor();
|
||||
fullScreenSwitch = settingsPane.findViewById(R.id.full_screen_switch);
|
||||
@@ -1318,6 +1319,93 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
/** Who sits in the Mascot style; the drop-down shows only while that style is chosen. */
|
||||
private static final int[] DRIVE_MODE_IDS = { R.id.drive_mode_eco, R.id.drive_mode_normal,
|
||||
R.id.drive_mode_sport, R.id.drive_mode_snow, R.id.drive_mode_custom };
|
||||
private static final int[] DRIVE_MODE_LABELS = { R.string.drive_mode_eco, R.string.drive_mode_normal,
|
||||
R.string.drive_mode_sport, R.string.drive_mode_snow, R.string.drive_mode_custom };
|
||||
/** True from a tap on a drive mode until the car's answer is in: one change at a time. */
|
||||
private final java.util.concurrent.atomic.AtomicBoolean driveModeBusy =
|
||||
new java.util.concurrent.atomic.AtomicBoolean();
|
||||
|
||||
/** The drive mode buttons on Settings: a tap asks the car, the highlight shows what it reports. */
|
||||
private void bindDriveMode() {
|
||||
for (int i = 0; i < DRIVE_MODE_IDS.length; i++) {
|
||||
final int code = DriveModeWrite.CODES[i];
|
||||
settingsPane.findViewById(DRIVE_MODE_IDS[i]).setOnClickListener(v -> requestDriveMode(code));
|
||||
}
|
||||
}
|
||||
|
||||
/** The highlight follows the car's reported mode, from every reading. */
|
||||
private void showDriveMode(Integer code) {
|
||||
if (settingsPane == null) return;
|
||||
int selected = DriveModeWrite.indexOf(code);
|
||||
for (int i = 0; i < DRIVE_MODE_IDS.length; i++) {
|
||||
settingsPane.findViewById(DRIVE_MODE_IDS[i]).setSelected(i == selected);
|
||||
}
|
||||
}
|
||||
|
||||
private String driveModeName(Integer code) {
|
||||
int i = DriveModeWrite.indexOf(code);
|
||||
return i >= 0 ? getString(DRIVE_MODE_LABELS[i]) : getString(R.string.drive_mode_unknown, String.valueOf(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the drive mode, on the settings thread: gear and speed read fresh (P, 0 km/h, or
|
||||
* nothing), the write itself behind EVHardware's standstill gate, then the car's mode read
|
||||
* back for up to three seconds. The status line says what the car reports, not what was asked.
|
||||
*/
|
||||
private void requestDriveMode(int code) {
|
||||
if (!driveModeBusy.compareAndSet(false, true)) return;
|
||||
TextView status = settingsPane.findViewById(R.id.drive_mode_status);
|
||||
String name = driveModeName(code);
|
||||
status.setText(getString(R.string.drive_mode_setting, name));
|
||||
try {
|
||||
settingsWriter.execute(() -> {
|
||||
String message;
|
||||
try {
|
||||
message = writeDriveMode(code, name);
|
||||
} catch (Throwable t) {
|
||||
message = getString(R.string.drive_mode_write_failed, name);
|
||||
}
|
||||
android.util.Log.i("NilsMG4.DriveMode", "Drive mode " + code + ": " + message);
|
||||
final String result = message;
|
||||
runOnUiThread(() -> {
|
||||
driveModeBusy.set(false);
|
||||
if (!isDestroyed()) status.setText(result);
|
||||
});
|
||||
});
|
||||
} catch (java.util.concurrent.RejectedExecutionException e) {
|
||||
driveModeBusy.set(false); // the activity is going away
|
||||
}
|
||||
}
|
||||
|
||||
private String writeDriveMode(int code, String name) throws InterruptedException {
|
||||
switch (DriveModeWrite.check(com.evsuite.hardware.EVHardware.INSTANCE.getVehicleSpeedKmh(),
|
||||
com.evsuite.hardware.EVHardware.INSTANCE.getGearRaw())) {
|
||||
case NOT_IN_P: return getString(R.string.drive_mode_not_in_p);
|
||||
case MOVING: return getString(R.string.drive_mode_moving);
|
||||
case UNREADABLE: return getString(R.string.drive_mode_unreadable);
|
||||
default: break;
|
||||
}
|
||||
com.evsuite.hardware.model.DriveMode mode = com.evsuite.hardware.model.DriveMode.Companion.ofRaw(code);
|
||||
if (mode == null || !com.evsuite.hardware.EVHardware.INSTANCE.writeDriveModeProperty(mode)) {
|
||||
return getString(R.string.drive_mode_write_failed, name);
|
||||
}
|
||||
Integer now = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Thread.sleep(300);
|
||||
now = com.evsuite.hardware.EVHardware.INSTANCE.getDriveModeRaw();
|
||||
if (now != null && now == code) break;
|
||||
}
|
||||
final Integer reported = now;
|
||||
runOnUiThread(() -> showDriveMode(reported));
|
||||
switch (DriveModeWrite.outcome(code, now)) {
|
||||
case APPLIED: return getString(R.string.drive_mode_applied, name);
|
||||
case NOT_APPLIED: return getString(R.string.drive_mode_not_applied, name, driveModeName(now));
|
||||
default: return getString(R.string.drive_mode_unread_back, name);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindMascotCharacter() {
|
||||
MascotCharacter[] characters = MascotCharacter.values();
|
||||
String[] labels = { getString(R.string.mascot_character_classic), getString(R.string.mascot_character_chibi),
|
||||
@@ -1808,6 +1896,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
showGauges(s);
|
||||
showCockpit(s);
|
||||
showDrawnStyles(s);
|
||||
showDriveMode(s.getDriveModeRaw());
|
||||
} catch (Throwable ignored) {
|
||||
// Leave the last good reading on screen.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user