App 2.16.1: Overview values every half second
The Overview read the car every 2 s, on the main thread. It now reads every 0.5 s on a background thread (one reader; a read still running makes the next tick skip rather than queue) and shows the values on the main thread. The consumption and charging trackers stay on the 2-second tick with the newest reading: they integrate over time, and the charging tracker writes its session to storage on every sample. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -109,6 +109,11 @@ 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
|
||||
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.
|
||||
- 2026-10-18 — App 2.16.1: the Overview reads the car every 0.5 s (was 2 s), on a background
|
||||
thread; the consumption and charging trackers stay on the 2-second tick (they integrate over
|
||||
time, and the charging tracker writes its session to storage on every sample). Note for
|
||||
emulator tests: uiautomator can no longer dump the screen while the Overview is open, since
|
||||
the UI is never idle.
|
||||
- 2026-10-18 — Grafana's read-only InfluxDB token without a manual step: an init container in
|
||||
the Grafana pod creates it with the admin token at every start, deletes the previous one and
|
||||
leaves only the read-only token in a memory volume; the datasource reads it with
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 84
|
||||
versionName "2.16.0"
|
||||
versionCode 85
|
||||
versionName "2.16.1"
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -87,6 +87,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
* page shows what the car reports, whether or not the upload service is running, and a
|
||||
* signal the vehicle does not publish must show a dash rather than a zero.
|
||||
*/
|
||||
/** Used only on the overview-read thread. */
|
||||
private EnergyTelemetryReader vehicleReader;
|
||||
private TextView vehSoc, vehRange, vehPower, vehSpeed, vehOdometer,
|
||||
vehCharging, vehHvac,
|
||||
@@ -214,7 +215,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
refreshStatus();
|
||||
refreshCallLog();
|
||||
refreshSteeringKeys();
|
||||
refreshVehicle();
|
||||
sampleTrackers();
|
||||
refreshConsumption();
|
||||
refreshWifi();
|
||||
uiHandler.postDelayed(this, 2_000L);
|
||||
@@ -330,6 +331,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onResume();
|
||||
// Poll state and log only while the screen is up; onPause cancels it.
|
||||
uiHandler.post(uiRefresh);
|
||||
uiHandler.post(overviewRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -376,6 +378,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onPause();
|
||||
// Stop polling off-screen: this activity has no reason to spend cycles then.
|
||||
uiHandler.removeCallbacks(uiRefresh);
|
||||
uiHandler.removeCallbacks(overviewRefresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
carReader.shutdownNow();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// ---------- Update check ----------
|
||||
@@ -1315,16 +1324,73 @@ public class MainActivity extends AppCompatActivity {
|
||||
* is not work to repeat every two seconds. A read that throws leaves the cells as they
|
||||
* were rather than blanking the page — a transient miss is not news worth showing.
|
||||
*/
|
||||
private void refreshVehicle() {
|
||||
// ---------- Overview: read every half second, off the main thread ----------
|
||||
|
||||
/** How often the Overview reads the car while it is on screen. */
|
||||
private static final long OVERVIEW_REFRESH_MS = 500L;
|
||||
|
||||
/** One reader thread; a read that is still running makes the next tick skip, not queue. */
|
||||
private final java.util.concurrent.ExecutorService carReader =
|
||||
java.util.concurrent.Executors.newSingleThreadExecutor(r -> new Thread(r, "overview-read"));
|
||||
private final java.util.concurrent.atomic.AtomicBoolean reading =
|
||||
new java.util.concurrent.atomic.AtomicBoolean();
|
||||
/** The newest reading, for the slower work on the 2-second tick. */
|
||||
private volatile EnergySnapshot latestSnapshot;
|
||||
|
||||
private final Runnable overviewRefresh = new Runnable() {
|
||||
@Override public void run() {
|
||||
readVehicle();
|
||||
uiHandler.postDelayed(this, OVERVIEW_REFRESH_MS);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private void readVehicle() {
|
||||
if (!reading.compareAndSet(false, true)) return;
|
||||
try {
|
||||
carReader.execute(() -> {
|
||||
try {
|
||||
if (vehicleReader == null) {
|
||||
vehicleReader = new EnergyTelemetryReader(getApplicationContext());
|
||||
}
|
||||
// The timestamp is passed explicitly: its Kotlin default is not visible from Java.
|
||||
EnergySnapshot s = vehicleReader.read(System.currentTimeMillis());
|
||||
latestSnapshot = s;
|
||||
runOnUiThread(() -> {
|
||||
if (!isDestroyed()) showVehicle(s);
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
// Leave the last good reading on screen.
|
||||
} finally {
|
||||
reading.set(false);
|
||||
}
|
||||
});
|
||||
} catch (java.util.concurrent.RejectedExecutionException e) {
|
||||
reading.set(false); // the activity is going away
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The consumption and charging trackers stay on the 2-second tick: they integrate over
|
||||
* time, so a faster rate adds nothing, and the charging tracker writes its session to
|
||||
* storage on every sample while charging.
|
||||
*/
|
||||
private void sampleTrackers() {
|
||||
EnergySnapshot s = latestSnapshot;
|
||||
if (s == null) return;
|
||||
try {
|
||||
if (vehicleReader == null) {
|
||||
vehicleReader = new EnergyTelemetryReader(getApplicationContext());
|
||||
}
|
||||
// The timestamp is passed explicitly: its Kotlin default is not visible from Java.
|
||||
EnergySnapshot s = vehicleReader.read(System.currentTimeMillis());
|
||||
ConsumptionTracker.get(this).sample(s);
|
||||
refreshCharging(s);
|
||||
} catch (Throwable ignored) {
|
||||
// A tracker failure must not stop the page.
|
||||
}
|
||||
}
|
||||
|
||||
private void showVehicle(EnergySnapshot s) {
|
||||
try {
|
||||
vehSoc.setText(fmt(s.getSocPercent(), "%.0f %%"));
|
||||
vehRange.setText(fmt(s.getRangeKm(), "%.0f km"));
|
||||
vehPower.setText(fmt(s.getBatteryPowerKw(), "%.1f kW"));
|
||||
|
||||
Reference in New Issue
Block a user