Ausrichtungs-Assistent fürs Rangieren

Begleitet das Einparken: verfolgt die Neigung über die Zeit, meldet ob es
besser oder schlechter wird, und erinnert an den flachsten Punkt - "vor 4
Sekunden stand das Fahrzeug 0,8 Grad flacher". Bei Erreichen der Toleranz
gibt es eine Vibration, das Display bleibt währenddessen wach.

Bewusst ohne Positionsbestimmung. Aus einem MEMS-Sensor lässt sich keine
brauchbare Strecke ableiten: der Fehler wächst beim zweifachen Integrieren
quadratisch mit der Zeit, und im Schritttempo gehen die tatsächlichen
Beschleunigungen im Rauschen unter. Gebraucht wird sie auch nicht - beim
Einparken lautet die Frage nie "wo stehe ich", sondern "wird es besser".
Das steckt vollständig im zeitlichen Verlauf der Neigung, ohne jede
Annahme über das Gelände. Die vorhandene Verlaufsaufzeichnung war mit
einem Punkt alle fünf Sekunden zu grob; der Assistent führt einen eigenen
Ringpuffer, der nur läuft solange die Ansicht offen ist.

Sind Spurweite und Radstand hinterlegt, kommt die nötige Höhe der
Auffahrkeile dazu. Das ist reine Geometrie und damit exakt. Die Masse
stehen im Fahrzeugprofil.

Dabei fiel auf, dass sich vorhandene Fahrzeuge praktisch nicht bearbeiten
liessen: das ging nur über eine Wischgeste, die niemand findet - erst
recht nicht, wenn dort jetzt die Masse einzutragen sind. Die Zeile hat nun
einen sichtbaren Knopf dafür, wie bei den WLAN-Einstellungen: antippen
wählt aus, das Zeichen daneben öffnet die Bearbeitung.

Ausserdem übernimmt der Assistent den anliegenden Messwert beim Öffnen.
Sonst stand dort bis zur nächsten Messung "Warte auf Messwerte", obwohl
längst welche vorlagen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BiasF
2026-08-30 15:52:57 +02:00
co-authored by Claude Opus 5
parent 826a201a36
commit 767fe4e798
9 changed files with 598 additions and 16 deletions
+69
View File
@@ -672,5 +672,74 @@ checkEqual("belegt fehlende Kalibrierung wird gemeldet",
uncalibrated.snapshot(deviceID: UUID(), rssi: nil).offReasons.first,
"Nicht kalibriert")
// MARK: 11 Ausrichtungs-Assistent
print("\nAusrichtungs-Assistent")
let start = Date(timeIntervalSince1970: 1_700_000_000)
var assistant = AlignmentAssistant()
checkEqual("ohne Messwerte kein Rat", assistant.trend, .unknown)
// Das Fahrzeug wird langsam flacher.
for (index, deviation) in [3.0, 2.6, 2.2, 1.8, 1.4, 1.0].enumerated() {
assistant.add(pitch: deviation, roll: 0, at: start.addingTimeInterval(Double(index) * 0.5))
}
checkEqual("fallende Abweichung wird als Verbesserung erkannt", assistant.trend, .improving)
checkEqual("kein Rückwärtshinweis, solange es besser wird",
assistant.improvementAtBest == nil, true)
checkEqual("Rat beim Verbessern", assistant.advice, "Wird besser weiter so")
// Jetzt am flachsten Punkt vorbei.
for (index, deviation) in [1.4, 1.9, 2.5].enumerated() {
assistant.add(pitch: deviation, roll: 0, at: start.addingTimeInterval(3.0 + Double(index) * 0.5))
}
checkEqual("steigende Abweichung wird als Verschlechterung erkannt", assistant.trend, .worsening)
checkEqual("bester Punkt wird gefunden", assistant.best?.deviation.rounded(), 1)
check("Rückwärtshinweis erscheint", assistant.improvementAtBest != nil)
checkEqual("Rat verweist auf den besseren Punkt",
assistant.advice.hasPrefix("Vor ") && assistant.advice.hasSuffix("zurück"), true)
checkEqual("Zeit seit dem besten Punkt",
assistant.timeSinceBest.map { ($0 * 10).rounded() / 10 }, 1.5)
// Erreicht das Fahrzeug die Waage, zählt nur noch das.
assistant.add(pitch: 0.2, roll: 0.1, at: start.addingTimeInterval(5))
checkEqual("Ziel erreicht", assistant.hasReachedTarget, true)
checkEqual("Rat beim Ziel", assistant.advice, "Steht eben anhalten")
// Gesamtabweichung über beide Achsen.
var both = AlignmentAssistant()
both.add(pitch: 3, roll: 4, at: start)
checkEqual("Gesamtabweichung ist der Betrag beider Achsen",
both.current?.deviation.rounded(), 5)
// Der Speicher endet nach der festgelegten Zeit.
var ageing = AlignmentAssistant()
ageing.add(pitch: 5, roll: 0, at: start)
ageing.add(pitch: 1, roll: 0, at: start.addingTimeInterval(AlignmentAssistant.memory + 10))
checkEqual("alte Messwerte fallen heraus", ageing.samples.count, 1)
// MARK: Auffahrkeile
print("\nAuffahrkeile")
// tan(2°) x 2,00 m = 6,99 cm
let acrossWedge = LevelingWedge.across(roll: 2.0, trackWidth: 2.0)
checkEqual("Keilhöhe quer",
acrossWedge.map { ($0.heightInCentimetres * 10).rounded() / 10 }, 7.0)
checkEqual("rechts höher heisst Keil nach links", acrossWedge?.side, .left)
checkEqual("links höher heisst Keil nach rechts",
LevelingWedge.across(roll: -2.0, trackWidth: 2.0)?.side, .right)
// tan(1,5°) x 3,50 m = 9,16 cm
let alongWedge = LevelingWedge.along(pitch: 1.5, wheelbase: 3.5)
checkEqual("Keilhöhe längs",
alongWedge.map { ($0.heightInCentimetres * 10).rounded() / 10 }, 9.2)
checkEqual("Heck höher heisst Keil nach vorne", alongWedge?.side, .front)
checkEqual("Front höher heisst Keil nach hinten",
LevelingWedge.along(pitch: -1.5, wheelbase: 3.5)?.side, .rear)
checkEqual("innerhalb der Toleranz kein Keil",
LevelingWedge.across(roll: 0.3, trackWidth: 2.0) == nil, true)
checkEqual("ohne Maß kein Keil",
LevelingWedge.across(roll: 3.0, trackWidth: 0) == nil, true)
print(failures == 0 ? "\nAlle Prüfungen bestanden." : "\n\(failures) Prüfung(en) fehlgeschlagen.")
exit(failures == 0 ? 0 : 1)