diff --git a/SPECS.md b/SPECS.md index 5423990..15496f6 100644 --- a/SPECS.md +++ b/SPECS.md @@ -44,6 +44,22 @@ car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go) ──▶ ABRP - [ ] Check Grafana's host in `k8s/ingress.yaml` (placeholder `car-grafana.k8s.stinnesbeck.com`). - [ ] First pairing and drive with 2.4.0: telemetry arrives in InfluxDB, Grafana and ABRP. +### Signals the car does not provide + +Checked 2026-10-17 against what actually reached InfluxDB (`schema.measurementFieldKeys`). The car +sends soc, est_battery_range, odometer, power, voltage, current, speed, hvac_setpoint, the four +tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does **not** send: + +- **Battery temperature:** read from `EV_BATTERY_AVERAGE_TEMPERATURE`, an Android 14 property; the + head unit runs Android 9, and the SAIC services offer none. +- **Cabin temperature:** `HVAC_TEMPERATURE_CURRENT` is not published; only the setpoint is. +- **Outside temperature:** not published by the vehicle (upstream noted the same). Upstream used + the head unit's weather service as a stand-in (`HeadUnitWeather`, removed in `ea8032a`); + restoring it was considered and declined. +- **Pack energy (`soe`) and capacity:** not published. The Live dashboard computes energy in pack + as SOC × usable capacity, from the hidden dashboard constant `usableKwh` (50.8 kWh for the + 51 kWh battery) in `k8s/grafana/dashboards/live.json`. + ## To do - [ ] **About dialog link and QR code → https://git.stinnesbeck.com/nils/mg4.** @@ -98,6 +114,9 @@ car ──live telemetry───▶ car.k8s.stinnesbeck.com (Go) ──▶ ABRP 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-17 — Live dashboard reworked around the signals the car sends: Climate setpoint, Last + update and a computed Energy in pack replace the empty temperature tiles; Battery voltage and + current replaces the Temperatures chart. - 2026-10-17 — Temporary on-screen diagnostics (b2a5b32) to debug blank values without adb; removed again (1002962) once values showed on the car. Revert 1002962 to bring them back. diff --git a/k8s/grafana/dashboards/live.json b/k8s/grafana/dashboards/live.json index 5046df4..73605d9 100644 --- a/k8s/grafana/dashboards/live.json +++ b/k8s/grafana/dashboards/live.json @@ -33,6 +33,14 @@ "current": {}, "hide": 0, "sort": 1 + }, + { + "name": "usableKwh", + "label": "Usable battery capacity (kWh)", + "type": "constant", + "query": "50.8", + "hide": 2, + "description": "MG4 51 kWh battery: 50.8 kWh usable." } ] }, @@ -187,7 +195,7 @@ { "id": 4, "type": "stat", - "title": "Battery temperature", + "title": "Climate setpoint", "datasource": { "type": "influxdb", "uid": "mg4-influxdb" @@ -205,7 +213,7 @@ "type": "influxdb", "uid": "mg4-influxdb" }, - "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"batt_temp\")\n |> last()" + "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"hvac_setpoint\")\n |> last()" } ], "fieldConfig": { @@ -235,7 +243,7 @@ { "id": 5, "type": "stat", - "title": "Outside temperature", + "title": "Last update", "datasource": { "type": "influxdb", "uid": "mg4-influxdb" @@ -253,17 +261,16 @@ "type": "influxdb", "uid": "mg4-influxdb" }, - "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"ext_temp\")\n |> last()" + "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"soc\")\n |> last()\n |> map(fn: (r) => ({_value: int(v: r._time) / 1000000}))" } ], "fieldConfig": { "defaults": { - "unit": "celsius", + "unit": "dateTimeFromNow", "color": { "mode": "fixed", "fixedColor": "#E0424C" - }, - "decimals": 0 + } }, "overrides": [] }, @@ -278,7 +285,8 @@ "colorMode": "value", "graphMode": "none", "textMode": "value" - } + }, + "description": "When the car last reported." }, { "id": 6, @@ -301,7 +309,7 @@ "type": "influxdb", "uid": "mg4-influxdb" }, - "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"soe\")\n |> last()" + "query": "from(bucket: \"mg4\")\n |> range(start: -30d)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and r._field == \"soc\")\n |> last()\n |> map(fn: (r) => ({_value: r._value / 100.0 * float(v: \"${usableKwh}\")}))" } ], "fieldConfig": { @@ -326,7 +334,8 @@ "colorMode": "value", "graphMode": "none", "textMode": "value" - } + }, + "description": "Computed: state of charge × usable capacity (dashboard constant usableKwh, 50.8 kWh for the 51 kWh battery). The car publishes no pack energy itself." }, { "id": 7, @@ -527,7 +536,7 @@ { "id": 10, "type": "timeseries", - "title": "Temperatures", + "title": "Battery voltage and current", "datasource": { "type": "influxdb", "uid": "mg4-influxdb" @@ -545,12 +554,12 @@ "type": "influxdb", "uid": "mg4-influxdb" }, - "query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"batt_temp\" or r._field == \"cabin_temp\" or r._field == \"ext_temp\" or r._field == \"hvac_setpoint\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])" + "query": "from(bucket: \"mg4\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"telemetry\" and r.installationId == \"${installationId}\" and (r._field == \"voltage\" or r._field == \"current\"))\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> keep(columns: [\"_time\", \"_field\", \"_value\"])" } ], "fieldConfig": { "defaults": { - "unit": "celsius", + "unit": "volt", "custom": { "lineWidth": 2, "fillOpacity": 8, @@ -558,7 +567,24 @@ "showPoints": "never" } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*current.*" + }, + "properties": [ + { + "id": "unit", + "value": "amp" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, "options": { "legend": { @@ -568,7 +594,8 @@ "tooltip": { "mode": "multi" } - } + }, + "description": "Traction battery. Current is negative while charging or regenerating." }, { "id": 11,