neraly complete Model of Driving done, but needs tweaking
This commit is contained in:
@@ -1,39 +1,227 @@
|
||||
# =============================
|
||||
# app/simulation/modules/gearbox.py
|
||||
# =============================
|
||||
|
||||
from __future__ import annotations
|
||||
from app.simulation.simulator import Module, Vehicle
|
||||
import math
|
||||
|
||||
GEARBOX_DEFAULTS = {
|
||||
# Übersetzungen
|
||||
"primary_ratio": 1.84, # Kurbelwelle -> Getriebeeingang
|
||||
# Gangübersetzungen (Index 0 = Neutral/N = 0.0)
|
||||
"gear_ratios": [0.0, 2.60, 1.90, 1.55, 1.35, 1.20, 1.07],
|
||||
# Ketten-/Endübersetzung via Zähne
|
||||
"front_sprocket_teeth": 16,
|
||||
"rear_sprocket_teeth": 45,
|
||||
|
||||
# Rad/Reifen
|
||||
"wheel_radius_m": 0.31, # dynamischer Halbmesser
|
||||
"drivetrain_efficiency": 0.93, # Wirkungsgrad Kurbel -> Rad
|
||||
"rpm_couple_gain": 0.20, # wie stark Engine-RPM zum Rad synchronisiert wird (0..1)
|
||||
|
||||
# Fahrzeug / Widerstände
|
||||
"rolling_c": 0.015, # Rollwiderstandskoeff.
|
||||
"air_density": 1.2, # kg/m^3
|
||||
"aero_cd": 0.6,
|
||||
"frontal_area_m2": 0.6,
|
||||
|
||||
# Kupplung (auto)
|
||||
"clutch_max_torque_nm": 220.0, # max übertragbares Drehmoment (bei c=1)
|
||||
"clutch_aggressiveness": 0.6, # 0..1 (0 = sehr sanft, 1 = sehr bissig)
|
||||
"clutch_curve": "linear", # "linear" | "progressive" | "soft"
|
||||
"clutch_drag_nm": 1.0, # Restschleppmoment bei getrennt
|
||||
"shift_time_s": 0.15, # Schaltzeit, während der entkuppelt wird
|
||||
"sync_rpm_band": 200.0, # RPM-Band, in dem als „synchron“ gilt
|
||||
|
||||
# Reifenhaftung (einfaches Limit)
|
||||
"tire_mu_peak": 1.10, # statischer Reibkoeffizient (Peak)
|
||||
"tire_mu_slide": 0.85, # Gleitreibung
|
||||
"rear_static_weight_frac": 0.60 # statischer Lastanteil auf Antriebsrad
|
||||
}
|
||||
|
||||
class GearboxModule(Module):
|
||||
PRIO = 30
|
||||
NAME = "gearbox"
|
||||
"""Koppelt Engine-RPM ↔ Wheel-Speed; registriert speed_kmh/gear fürs Dashboard."""
|
||||
|
||||
def __init__(self):
|
||||
self.speed_tau = 0.3
|
||||
self.rpm_couple = 0.2
|
||||
# interner Zustand
|
||||
self._clutch = 0.0 # 0..1
|
||||
self._shift_t = 0.0
|
||||
self._target_gear = None
|
||||
self._wheel_v = 0.0 # m/s
|
||||
|
||||
def apply(self, v: Vehicle, dt: float) -> None:
|
||||
# Dashboard registration
|
||||
v.register_metric("speed_kmh", label="Geschwindigkeit", unit="km/h", fmt=".1f", source="gearbox", priority=30)
|
||||
v.register_metric("gear", label="Gang", source="gearbox", priority=25)
|
||||
# --- Dashboard-Registrierungen ---
|
||||
v.register_metric("speed_kmh", label="Geschwindigkeit", unit="km/h", fmt=".1f", source="gearbox", priority=30)
|
||||
v.register_metric("gear", label="Gang", fmt="", source="gearbox", priority=25)
|
||||
v.register_metric("clutch_pct", label="Kupplung", unit="%", fmt=".0f", source="gearbox", priority=26)
|
||||
v.register_metric("wheel_slip_pct", label="Reifenschlupf", unit="%", fmt=".0f", source="gearbox", priority=27)
|
||||
|
||||
g = int(v.ensure("gear", 0))
|
||||
rpm = float(v.ensure("rpm", 1200))
|
||||
speed = float(v.ensure("speed_kmh", 0.0))
|
||||
ratios = v.config.get("gearbox", {}).get("kmh_per_krpm", [0.0])
|
||||
# --- Config / Inputs ---
|
||||
gb = dict(GEARBOX_DEFAULTS)
|
||||
gb.update(v.config.get("gearbox", {}))
|
||||
|
||||
if g <= 0 or g >= len(ratios):
|
||||
speed = max(0.0, speed - 6.0*dt)
|
||||
v.set("speed_kmh", speed)
|
||||
return
|
||||
primary = float(gb["primary_ratio"])
|
||||
gear_ratios = list(gb["gear_ratios"])
|
||||
z_f = int(gb["front_sprocket_teeth"])
|
||||
z_r = int(gb["rear_sprocket_teeth"])
|
||||
final = (z_r / max(1, z_f))
|
||||
|
||||
kmh_per_krpm = float(ratios[g])
|
||||
target_speed = (rpm/1000.0) * kmh_per_krpm
|
||||
alpha = min(1.0, dt / max(0.05, self.speed_tau))
|
||||
speed = (1-alpha) * speed + alpha * target_speed
|
||||
v.set("speed_kmh", speed)
|
||||
r_w = float(gb["wheel_radius_m"])
|
||||
eta = float(gb["drivetrain_efficiency"])
|
||||
couple_gain = float(gb["rpm_couple_gain"])
|
||||
|
||||
wheel_rpm = (speed / max(0.1, kmh_per_krpm)) * 1000.0
|
||||
rpm = (1-self.rpm_couple) * rpm + self.rpm_couple * wheel_rpm
|
||||
v.set("rpm", int(rpm))
|
||||
c_rr = float(gb["rolling_c"])
|
||||
rho = float(gb["air_density"])
|
||||
cd = float(gb["aero_cd"])
|
||||
A = float(gb["frontal_area_m2"])
|
||||
|
||||
clutch_Tmax = float(gb["clutch_max_torque_nm"])
|
||||
clutch_agr = min(1.0, max(0.0, float(gb["clutch_aggressiveness"])))
|
||||
clutch_curve= str(gb["clutch_curve"]).lower()
|
||||
clutch_drag = float(gb["clutch_drag_nm"])
|
||||
shift_time = float(gb["shift_time_s"])
|
||||
sync_band = float(gb["sync_rpm_band"])
|
||||
|
||||
mu_peak = float(gb["tire_mu_peak"])
|
||||
mu_slide= float(gb["tire_mu_slide"])
|
||||
rear_w = float(gb["rear_static_weight_frac"])
|
||||
|
||||
m = float(v.config.get("vehicle", {}).get("mass_kg", 210.0))
|
||||
g = 9.81
|
||||
|
||||
# State
|
||||
gear = int(v.ensure("gear", 0))
|
||||
ign = str(v.ensure("ignition", "OFF"))
|
||||
rpm = float(v.ensure("rpm", 1200.0))
|
||||
pedal= float(v.ensure("throttle_pedal_pct", 0.0))
|
||||
pedal = max(0.0, min(100.0, pedal))
|
||||
|
||||
# verfügbare Motordaten
|
||||
eng_avail_T = float(v.get("engine_available_torque_nm", 0.0)) # „kann liefern“
|
||||
# Hinweis: die Engine zieht später v.acc_total("engine.torque_load_nm") ab.
|
||||
|
||||
# Pending Shift Commands (vom UI gesetzt und dann zurücksetzen)
|
||||
up_req = bool(v.ensure("gear_shift_up", False))
|
||||
down_req = bool(v.ensure("gear_shift_down", False))
|
||||
to_N_req = bool(v.ensure("gear_set_neutral", False))
|
||||
if up_req: v.set("gear_shift_up", False)
|
||||
if down_req: v.set("gear_shift_down", False)
|
||||
if to_N_req: v.set("gear_set_neutral", False)
|
||||
|
||||
# --- Schaltlogik ---
|
||||
if self._shift_t > 0.0:
|
||||
self._shift_t -= dt
|
||||
# währenddessen Kupplung öffnen
|
||||
self._clutch = max(0.0, self._clutch - self._rate_from_agr(1.0, clutch_agr) * dt)
|
||||
if self._shift_t <= 0.0 and self._target_gear is not None:
|
||||
gear = int(self._target_gear)
|
||||
v.set("gear", gear)
|
||||
self._target_gear = None
|
||||
else:
|
||||
# neue Requests annehmen, wenn nicht bereits am Limit
|
||||
if to_N_req:
|
||||
self._target_gear = 0
|
||||
self._shift_t = shift_time
|
||||
elif up_req and gear < min(6, len(gear_ratios)-1):
|
||||
self._target_gear = gear + 1
|
||||
self._shift_t = shift_time
|
||||
elif down_req and gear > 0:
|
||||
self._target_gear = gear - 1
|
||||
self._shift_t = shift_time
|
||||
|
||||
# --- Gesamtübersetzung und Soll-Drehzahlbezug ---
|
||||
gear_ratio = float(gear_ratios[gear]) if 0 <= gear < len(gear_ratios) else 0.0
|
||||
overall = primary * gear_ratio * final # Kurbel -> Rad
|
||||
wheel_omega = self._wheel_v / max(1e-6, r_w) # rad/s
|
||||
eng_omega_from_wheel = wheel_omega * overall
|
||||
rpm_from_wheel = eng_omega_from_wheel * 60.0 / (2.0 * math.pi)
|
||||
|
||||
# --- Kupplungs-Automat ---
|
||||
# Zielschließung aus Schlupf und Fahrerwunsch
|
||||
slip_rpm = abs(rpm - rpm_from_wheel)
|
||||
slip_norm = min(1.0, slip_rpm / max(1.0, sync_band))
|
||||
base_target = max(0.0, min(1.0, (pedal/100.0)*0.6 + (1.0 - slip_norm)*0.6))
|
||||
target_c = self._shape(base_target, clutch_curve)
|
||||
|
||||
# Bei N oder ohne Übersetzung kein Kraftschluss
|
||||
if gear == 0 or overall <= 1e-6 or ign in ("OFF","ACC"):
|
||||
target_c = 0.0
|
||||
|
||||
# Sanfte Anti-Abwürg-Logik: ist RPM sehr niedrig und Radlast hoch -> etwas öffnen
|
||||
if rpm < 1500.0 and slip_rpm > 200.0:
|
||||
target_c = min(target_c, 0.6)
|
||||
|
||||
# Dynamik der Kupplung (Annäherung Richtung target_c)
|
||||
rate = self._rate_from_agr(target_c, clutch_agr) # s^-1
|
||||
self._clutch += (target_c - self._clutch) * min(1.0, rate * dt)
|
||||
self._clutch = max(0.0, min(1.0, self._clutch))
|
||||
|
||||
# --- Übertragbares Motormoment durch Kupplung ---
|
||||
clutch_cap = clutch_Tmax * self._clutch
|
||||
T_engine_to_input = max(0.0, min(eng_avail_T, clutch_cap))
|
||||
|
||||
# --- Rad-Seite: aus Motor via Übersetzung ---
|
||||
T_wheel_from_engine = T_engine_to_input * overall * eta if overall > 0.0 else 0.0 # Nm am Rad
|
||||
|
||||
# --- Reibungs-/Luftwiderstand ---
|
||||
v_ms = max(0.0, self._wheel_v)
|
||||
F_roll = m * g * c_rr
|
||||
F_aero = 0.5 * rho * cd * A * v_ms * v_ms
|
||||
F_res = F_roll + F_aero
|
||||
|
||||
# --- Reifen-Force-Limit & Schlupf ---
|
||||
N_rear = m * g * rear_w
|
||||
F_trac_cap = mu_peak * N_rear
|
||||
F_from_engine = T_wheel_from_engine / max(1e-6, r_w)
|
||||
|
||||
slip = 0.0
|
||||
F_trac = F_from_engine
|
||||
if abs(F_from_engine) > F_trac_cap:
|
||||
slip = min(1.0, (abs(F_from_engine) - F_trac_cap) / max(1.0, F_from_engine))
|
||||
# im Schlupf auf Slide-Niveau kappen
|
||||
F_trac = math.copysign(mu_slide * N_rear, F_from_engine)
|
||||
|
||||
# --- Fahrzeugdynamik: a = (F_trac - F_res)/m ---
|
||||
a = (F_trac - F_res) / max(1.0, m)
|
||||
self._wheel_v = max(0.0, self._wheel_v + a * dt)
|
||||
speed_kmh = self._wheel_v * 3.6
|
||||
v.set("speed_kmh", float(speed_kmh))
|
||||
v.set("gear", int(gear))
|
||||
v.set("clutch_pct", float(self._clutch * 100.0))
|
||||
v.set("wheel_slip_pct", float(max(0.0, min(1.0, slip)) * 100.0))
|
||||
|
||||
# --- Reaktionsmoment zurück an den Motor (Last) ---
|
||||
# aus tatsächlich wirkender Traktionskraft (nach Grip-Limit)
|
||||
T_engine_load = 0.0
|
||||
if overall > 0.0 and self._clutch > 0.0:
|
||||
T_engine_load = (abs(F_trac) * r_w) / max(1e-6, (overall * eta))
|
||||
# kleiner Schlepp bei getrennt
|
||||
if self._clutch < 0.1:
|
||||
T_engine_load += clutch_drag * (1.0 - self._clutch)
|
||||
|
||||
if T_engine_load > 0.0:
|
||||
v.push("engine.torque_load_nm", +T_engine_load, source="driveline")
|
||||
|
||||
# --- RPM-Kopplung (sanfte Synchronisierung) ---
|
||||
if overall > 0.0 and self._clutch > 0.2 and ign in ("ON","START"):
|
||||
alpha = min(1.0, couple_gain * self._clutch * dt / max(1e-3, 0.1))
|
||||
rpm = (1.0 - alpha) * rpm + alpha * rpm_from_wheel
|
||||
v.set("rpm", float(rpm))
|
||||
|
||||
# ----- Helpers -----
|
||||
def _rate_from_agr(self, target_c: float, agr: float) -> float:
|
||||
"""Engage/Release-Geschwindigkeit [1/s] in Abhängigkeit der Aggressivität."""
|
||||
# 0.05s (bissig) bis 0.5s (sanft) für ~63%-Annäherung
|
||||
tau = 0.5 - 0.45 * agr
|
||||
if target_c < 0.1: # Öffnen etwas flotter
|
||||
tau *= 0.7
|
||||
return 1.0 / max(0.05, tau)
|
||||
|
||||
def _shape(self, x: float, curve: str) -> float:
|
||||
x = max(0.0, min(1.0, x))
|
||||
if curve == "progressive":
|
||||
return x * x
|
||||
if curve == "soft":
|
||||
return math.sqrt(x)
|
||||
return x # linear
|
||||
|
Reference in New Issue
Block a user