Fehlerspeicher, Amber statt Gruen, Tankbalken rot ab 90 km

- Neue Sensorrolle FEHLERSPEICHER_SENSOR (number_of_diagnostic_trouble_codes
  vom CAN) und eine fuenfte Zeile auf der Sicherheitsseite: "Keine
  Fehlermeldungen". Bewusst NICHT im _sicherheitscheck(): "gesichert"
  rechnet ein UND ueber diese Liste, und ein Eintrag im Fehlerspeicher sagt
  nichts darueber, ob der Wagen verschlossen dasteht.
- "Zuendung an" ist Amber statt Gruen. Gruen las sich als "alles in Ordnung
  und sicher"; unterwegs ist kein guter Zustand, sondern ein anderer.
- Das Modell-Auswahlfeld bekam dm-eingabe statt dm-auswahl - nur letzteres
  setzt appearance:none, deshalb zeichnete der Browser seinen nativen Pfeil
  neben den des Design-Systems. Die Breitenregel heisst jetzt
  dm-feld--fahrzeug und gilt fuer input und select.
- "aktuell" erscheint erst nach einer Pruefung, und fuer beide Abschnitte
  gemeinsam. Der App-Abschnitt zeigte die Marke schon, sobald ein Buendel
  vorlag - also unmittelbar nach einem Update wieder.
- Der Tankbalken wird rot ab 90 km Restreichweite, in Kilometern und nicht
  in Prozent: die Reichweite ist die Aussage, um die es geht, und das
  Fahrzeug rechnet die Fahrweise darin bereits ein. Farbe --red (Audi-CI,
  #F50537), nicht --bad-flaeche - letzteres ist fuer weisse Schrift darauf
  gewaehlt, und hier steht keine drauf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-05 17:31:52 +02:00
parent 255a593b50
commit e76252505f
14 changed files with 258 additions and 20 deletions
+3
View File
@@ -127,6 +127,9 @@ export interface Fahrzeugstatus {
batteriespannung?: number | null;
gesichert?: boolean | null;
sicherheitscheck?: SicherheitsPunkt[];
/** Diagnosecodes im Fehlerspeicher. Getrennt vom Sicherheitscheck, weil ein
Eintrag nichts darueber sagt, ob der Wagen verschlossen dasteht. */
fehlerspeicher?: { anzahl: number | null; ok: boolean | null } | null;
/** Zustand des Zündungssensors — ausdrücklich nicht „fährt": es gibt keinen
Bewegungssensor, und die Zündung meldet an auch bei Zubehörstellung oder
im Stand. `null`/fehlend heißt „kein Zündungssensor zugeordnet", also
+4
View File
@@ -66,6 +66,9 @@ export interface Fahrzeug {
batteriespannung: number | null
gesichert: boolean | null
sicherheitscheck: SicherheitsPunkt[]
/** Diagnosecodes im Fehlerspeicher; `null` heisst "kein Sensor zugeordnet
oder er meldet nichts" - nie als "keine Fehler" deuten. */
fehlerspeicher: { anzahl: number | null; ok: boolean | null } | null
/** Zustand des Zündungssensors — nicht „fährt": ohne Bewegungssensor sagt
an nur, dass die Zündung an ist. `null` heißt „kein Zündungssensor
zugeordnet", also unbekannt — nicht „steht". */
@@ -181,6 +184,7 @@ export function profilZuFahrzeug(profil: Profil, status: Fahrzeugstatus): Fahrze
batteriespannung: status.batteriespannung ?? null,
gesichert: status.gesichert ?? null,
sicherheitscheck: status.sicherheitscheck ?? [],
fehlerspeicher: status.fehlerspeicher ?? null,
zuendung: status.zuendung ?? null,
geparktSeit: (status.geparkt_seit as string | null) ?? null,
faehrtSeit: (status.faehrt_seit as string | null) ?? null,
+1
View File
@@ -26,6 +26,7 @@ function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug {
batteriespannung: null,
gesichert: null,
sicherheitscheck: [],
fehlerspeicher: null,
zuendung: null,
geparktSeit: null,
faehrtSeit: null,
+26 -10
View File
@@ -412,8 +412,13 @@ export function Einstellungen({
gesucht. Die Liste selbst steht in daten/fahrzeugmodelle.ts und
wird von einem Test gegen die Panel-Fassung geprüft. */}
<Feld label="Modell">
{/* dm-auswahl, NICHT dm-eingabe: nur dort steht `appearance: none`.
Der Aufklapp-Pfeil kommt als Hintergrundbild aus dem
Design-System (.ads-feld select); ohne den Reset zeichnet der
Browser seinen eigenen daneben — vom Eigentümer am 05.09.2026
als „Doppelpfeil" gemeldet. */}
<select
className="dm-eingabe dm-eingabe--fahrzeug"
className="dm-auswahl dm-feld--fahrzeug"
value={werte.fahrzeugtitel}
onChange={(e) => {
aendern({ fahrzeugtitel: e.target.value })
@@ -429,7 +434,7 @@ export function Einstellungen({
</Feld>
<Feld label="Kennzeichen">
<input
className="dm-eingabe dm-eingabe--fahrzeug"
className="dm-eingabe dm-feld--fahrzeug"
value={werte.kennzeichen}
onChange={(e) => aendern({ kennzeichen: e.target.value })}
onBlur={() => sichern()}
@@ -437,7 +442,7 @@ export function Einstellungen({
</Feld>
<Feld label="Erstzulassung">
<input
className="dm-eingabe dm-eingabe--fahrzeug"
className="dm-eingabe dm-feld--fahrzeug"
placeholder="TT.MM.JJJJ"
value={erstzulassungWert}
onChange={(e) => setzeErstzulassung(e.target.value)}
@@ -446,7 +451,7 @@ export function Einstellungen({
</Feld>
<Feld label="Ausführung" last>
<input
className="dm-eingabe dm-eingabe--fahrzeug"
className="dm-eingabe dm-feld--fahrzeug"
value={werte.ausfuehrung}
onChange={(e) => aendern({ ausfuehrung: e.target.value })}
onBlur={() => sichern()}
@@ -844,7 +849,13 @@ export function Einstellungen({
</>
) : (
<>
{integrationUpdate && <span className="dm-standmarke">aktuell</span>}
{/* Nur nach einer Prüfung. „aktuell" ohne Prüfung wäre eine
Behauptung ohne Grundlage — dieselbe Bedingung trägt der
App-Abschnitt darunter, damit beide gemeinsam grün werden
(Vorgabe des Eigentümers, 05.09.2026). */}
{integrationUpdate?.geprueft_am && (
<span className="dm-standmarke">aktuell</span>
)}
</>
)}
{updateAktionFehler && <p className="dm-fehler">{updateAktionFehler}</p>}
@@ -890,11 +901,16 @@ export function Einstellungen({
nach dem Neustart etwas. */
<p className="dm-erklaerung">Update {otaBuendel.version} vorhanden</p>
) : otaBuendel ? (
/* Dieselbe grüne Marke wie bei der Integration, statt eines
Satzes - Vorgabe des Eigentümers, 05.09.2026. Ohne Zeitpunkt:
anders als bei der Integration wird hier nicht auf Knopfdruck
geprüft, das Bündel meldet der Server beim Laden mit. */
<span className="dm-standmarke">aktuell</span>
/* Dieselbe grüne Marke wie bei der Integration - und an
dieselbe Bedingung geknüpft: erst nach einer Prüfung. Das
ndel meldet der Server zwar bei jedem Laden mit, aber
„aktuell" stand damit schon unmittelbar nach einem Update
wieder da, ohne dass jemand nachgesehen hätte (vom Eigentümer
am 05.09.2026 bemerkt). Der eine Knopf unten prüft beides,
also werden auch beide zusammen grün. */
integrationUpdate?.geprueft_am ? (
<span className="dm-standmarke">aktuell</span>
) : null
) : (
<p className="dm-erklaerung">
Der Server hat gerade kein Update-Bündel hinterlegt.
+21 -1
View File
@@ -44,6 +44,17 @@ export function Fahrzeugstatus({ geheZu }: { geheZu: (name: SeitenName) => void
Schlosssensoren melden — nur die Überschrift darf nicht „abgestellt"
behaupten, während die Zündung an ist. */
const faehrt = standortZustand(fahrzeug).faehrt
const fehlerAnzahl =
typeof fahrzeug.fehlerspeicher?.anzahl === "number" ? fahrzeug.fehlerspeicher.anzahl : null
const fehlerOk = fehlerAnzahl === null ? null : fehlerAnzahl === 0
const fehlerText =
fehlerAnzahl === null
? "Fehlerspeicher unbekannt"
: fehlerAnzahl === 0
? "Keine Fehlermeldungen"
: fehlerAnzahl === 1
? "1 Fehlermeldung"
: `${fehlerAnzahl} Fehlermeldungen`
const sammelText = faehrt
? "Zündung an - Fahrzeug in Betrieb"
: gesichert === null
@@ -68,7 +79,7 @@ export function Fahrzeugstatus({ geheZu }: { geheZu: (name: SeitenName) => void
{/* Während der Fahrt weder Haken noch Kreuz noch Fragezeichen, sondern
derselbe Punkt wie im Standortmenü — der Zustand ist dann nicht
„geprüft", sondern schlicht ein anderer. */}
{faehrt ? <span className="ads-dot" /> : <StatusKreis ok={gesichert} />}
{faehrt ? <span className="ads-dot ads-dot--warn" /> : <StatusKreis ok={gesichert} />}
<span>{sammelText}</span>
</Tile>
@@ -105,6 +116,15 @@ export function Fahrzeugstatus({ geheZu }: { geheZu: (name: SeitenName) => void
{licht === null ? "Licht unbekannt" : licht ? "Licht ausgeschaltet" : "Licht eingeschaltet"}
</span>
</div>
{/* Der Fehlerspeicher des Fahrzeugs. Steht bewusst NICHT im
Sicherheitscheck und geht damit auch nicht in „sicher
abgestellt" ein — ein Eintrag sagt nichts darüber, ob der Wagen
verschlossen dasteht (siehe _fehlerspeicher() im Backend).
WELCHER Fehler vorliegt, meldet das Gerät nicht. */}
<div className="dm-sicherheitszeile">
<StatusKreis ok={fehlerOk} />
<span className="dm-sicherheitszeile__text">{fehlerText}</span>
</div>
</div>
</Tile>
</>
+28 -2
View File
@@ -29,6 +29,8 @@ import { Typenschild, schildErkennen } from "./Typenschild"
// links der Kennzahl - deckungsgleich mit ART_ICON im Panel (vHome()). Die
// Hauptuntersuchung hat kein Restkilometer-Ziel und zeigt stattdessen den
// Art-Namen selbst unter dem (gleich großen) Datum.
const RESERVE_KM = 90
const SERVICE_SYMBOL: Partial<Record<string, typeof SymbolOelwechsel>> = {
"Ölwechsel": SymbolOelwechsel,
"Inspektion": SymbolInspektion,
@@ -102,9 +104,30 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string)
Wortlaut wie im Standortmenü: „Zündung an", nicht „fährt" — einen
Bewegungssensor gibt es nicht, die Zündung steht auch im Stand mit
laufendem Motor auf an. Deckungsgleich mit vHome() im Panel. */
/* Ab wann der Tankbalken rot wird - in KILOMETERN Restreichweite, nicht in
Prozent. Die Reichweite ist die Aussage, um die es geht ("wie weit komme ich
noch"), und das Fahrzeug rechnet die Fahrweise darin bereits ein; eine
Prozentschwelle haette bei 1,71 bis 12,53 km je Liter (in 45 Tagen gemessen)
bei ganz unterschiedlichen Restreichweiten ausgeloest.
90 km ist die Zahl des Eigentuemers, an der myAudi-App beobachtet. Belegt ist
daraus nur eine Untergrenze: sein Bildschirmfoto zeigt 70 km rot, ein Wert
darueber wurde nie im grauen Zustand festgehalten. Der Dongle liefert kein
Reserve- oder Warnsignal (alle 54 Entitaeten geprueft), die Schwelle der
myAudi-App steht also in keinen Daten, die wir haben - dies ist eine
Festlegung, keine Messung.
Ohne bekannte Reichweite bleibt der Balken neutral: eine Warnung aus
fehlenden Daten waere geraten. */
const tankReserve =
typeof fahrzeug.reichweite === "number" && fahrzeug.reichweite <= RESERVE_KM
const faehrt = standortZustand(fahrzeug).faehrt
/* Amber, nicht grün: „Zündung an" ist kein guter Zustand, sondern ein
anderer — grün läse sich als „alles in Ordnung und sicher" (Vorgabe des
Eigentümers, 05.09.2026). */
const sicherheitsstatus = faehrt
? "ok"
? "warn"
: fahrzeug.gesichert === true
? "ok"
: fahrzeug.gesichert === false
@@ -190,7 +213,10 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string)
{fahrzeug.tankBekannt ? `${de(fahrzeug.tankPct)} %` : ""}
</span>
</div>
<ProgressBar percent={Math.max(0, Math.min(100, fahrzeug.tankPct))} />
<ProgressBar
percent={Math.max(0, Math.min(100, fahrzeug.tankPct))}
className={tankReserve ? "dm-balken--reserve" : ""}
/>
<span className="ads-eyebrow" style={{ marginTop: "var(--sp-3)" }}>
Reichweite
</span>
+16 -1
View File
@@ -37,7 +37,8 @@
Oberflächen: das längste Label ("Erstzulassung") lässt in der App nur
183px fürs Feld, im Panel 196px. 180 passt in beiden mit etwas Luft und
hält sie zugleich untereinander gleich. */
.ads-feld input.dm-eingabe--fahrzeug {
.ads-feld input.dm-feld--fahrzeug,
.ads-feld select.dm-feld--fahrzeug {
width: 180px;
flex: 0 0 auto;
max-width: none;
@@ -2383,3 +2384,17 @@ input[type="range"] {
/* Spinner UND Beschriftung in EINEM Knopf: seit dem 05.09.2026 trägt der
Knopf der Update-Kachel den laufenden Vorgang selbst, statt dass daneben
eine eigene Ladezeile steht (Vorgabe des Eigentümers: „warum nicht den
update suchen button dafür verwenden"). */
.dm-knopflader {
display: inline-flex;
align-items: center;
gap: 10px;
}
/* Tankbalken in der Reserve: das Audi-CI-Rot (--red, #F50537), nicht
--bad-flaeche (#D70015). Letzteres ist Apples dunkles Rot und ist so
gewaehlt, dass WEISSE SCHRIFT darauf lesbar bleibt - hier steht keine
Schrift auf der Flaeche, der Grund faellt also weg. Der Balken bildet die
myAudi-App nach, und deren Rot ist das der Marke (vom Eigentuemer am
05.09.2026 als "weniger dunkel" gemeldet).