mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-14 13:52:12 +08:00
FLM
This commit is contained in:
Binary file not shown.
@@ -319,10 +319,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ForceStopDistanceOffset", {PERSISTENT, INT, "0", "0", 2}},
|
||||
{"ForceStandstill", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FTMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"FTMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"FTMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"FTMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
|
||||
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"FLMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"FLMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"FPSCounter", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"GalaxyDashboardStats", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
|
||||
{"StarPilotApiToken", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
|
||||
Binary file not shown.
@@ -106,6 +106,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument("--consistent-detections", type=int, help="Override matching reads required for an initial publication.")
|
||||
parser.add_argument("--change-consistent-detections", type=int, help="Override matching reads required to change a publication.")
|
||||
parser.add_argument("--change-single-read-min-confidence", type=float, help="Override confidence for a one-read speed change.")
|
||||
parser.add_argument("--change-repeat-min-confidence", type=float, help="Override confidence required from the confirming speed-change read.")
|
||||
parser.add_argument("--max-cases", type=int, default=0, help="Optional evaluation cap after deduplication.")
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -272,6 +273,8 @@ def main() -> int:
|
||||
slv.CHANGE_CONSISTENT_DETECTIONS = args.change_consistent_detections
|
||||
if args.change_single_read_min_confidence is not None:
|
||||
slv.CHANGE_SINGLE_READ_MIN_CONFIDENCE = args.change_single_read_min_confidence
|
||||
if args.change_repeat_min_confidence is not None:
|
||||
slv.CHANGE_REPEAT_MIN_CONFIDENCE = args.change_repeat_min_confidence
|
||||
cases = load_cases(queue_path, labels_path, args.dedupe_seconds)
|
||||
if args.focus_eval_csv:
|
||||
with args.focus_eval_csv.expanduser().resolve().open(encoding="utf-8", newline="") as input_file:
|
||||
@@ -397,6 +400,7 @@ def main() -> int:
|
||||
"low_speed_change_consistent_detections": slv.LOW_SPEED_CHANGE_CONSISTENT_DETECTIONS,
|
||||
"low_speed_change_min_confidence": slv.LOW_SPEED_CHANGE_MIN_CONFIDENCE,
|
||||
"change_single_read_min_confidence": slv.CHANGE_SINGLE_READ_MIN_CONFIDENCE,
|
||||
"change_repeat_min_confidence": slv.CHANGE_REPEAT_MIN_CONFIDENCE,
|
||||
"low_speed_change_allow_strong_consensus": slv.LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS,
|
||||
"strong_model_consensus_enabled": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_CONSENSUS_ENABLED,
|
||||
"strong_model_min_proposal_confidence": slv.DETECTOR_CLASSIFIER_STRONG_MODEL_MIN_PROPOSAL_CONFIDENCE,
|
||||
|
||||
@@ -90,7 +90,7 @@ class LatControlTorque(LatControl):
|
||||
self.is_silverado = CP.carFingerprint in SILVERADO_CARS
|
||||
self.is_gm = CP.brand == "gm"
|
||||
self.is_hkg_canfd_torque = CP.brand == "hyundai" and bool(CP.flags & HyundaiFlags.CANFD)
|
||||
self.ftm_surface_profile_key = get_ftm_surface_profile_key(CP.carFingerprint, torque_control=True)
|
||||
self.flm_surface_profile_key = get_flm_surface_profile_key(CP.carFingerprint, torque_control=True)
|
||||
if self.is_ioniq_6:
|
||||
self.low_speed_reset_threshold = min(self.low_speed_reset_threshold, IONIQ_6_LOW_SPEED_PID_RESET_SPEED)
|
||||
self.use_bolt_ff_scaling = self.is_bolt_2022_2023 or self.is_bolt_2018_2021 or self.is_bolt_2017
|
||||
@@ -158,10 +158,10 @@ class LatControlTorque(LatControl):
|
||||
def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay, calibrated_pose, model_data, starpilot_toggles):
|
||||
pid_log = log.ControlsState.LateralTorqueState.new_message()
|
||||
pid_log.version = VERSION
|
||||
ftm_profile_active = bool(getattr(starpilot_toggles, "ftm_trial_applied", False) and
|
||||
getattr(starpilot_toggles, "ftm_active_profile_id", ""))
|
||||
set_ftm_runtime_overrides(getattr(starpilot_toggles, "ftm_active_overrides", None) if ftm_profile_active else None)
|
||||
ftm_surface_active = ftm_profile_active and ftm_runtime_overrides_active()
|
||||
flm_profile_active = bool(getattr(starpilot_toggles, "flm_trial_applied", False) and
|
||||
getattr(starpilot_toggles, "flm_active_profile_id", ""))
|
||||
set_flm_runtime_overrides(getattr(starpilot_toggles, "flm_active_overrides", None) if flm_profile_active else None)
|
||||
flm_surface_active = flm_profile_active and flm_runtime_overrides_active()
|
||||
if not active:
|
||||
output_torque = 0.0
|
||||
pid_log.active = False
|
||||
@@ -329,15 +329,15 @@ class LatControlTorque(LatControl):
|
||||
friction_threshold = CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD
|
||||
friction_scale = get_civic_bosch_modified_b_friction_scale(CS.vEgo, setpoint, desired_lateral_jerk)
|
||||
friction_scale = 1.0 + ((friction_scale - 1.0) * civic_bosch_modified_a_center_taper)
|
||||
if ftm_surface_active and self.ftm_surface_profile_key and not ioniq_6_active:
|
||||
universal_ftm_profile = self.ftm_surface_profile_key == FTM_UNIVERSAL_PROFILE_KEY
|
||||
ftm_full_surface_center_taper = get_ftm_full_surface_center_taper_scale(self.ftm_surface_profile_key, setpoint, CS.vEgo,
|
||||
include_base_center=universal_ftm_profile)
|
||||
ff *= get_ftm_full_surface_ff_scale(self.ftm_surface_profile_key, setpoint, desired_lateral_jerk, CS.vEgo,
|
||||
include_base_ff=universal_ftm_profile) * ftm_full_surface_center_taper
|
||||
friction_threshold = get_ftm_full_surface_friction_threshold(self.ftm_surface_profile_key, friction_threshold, CS.vEgo,
|
||||
if flm_surface_active and self.flm_surface_profile_key and not ioniq_6_active:
|
||||
universal_flm_profile = self.flm_surface_profile_key == FLM_UNIVERSAL_PROFILE_KEY
|
||||
flm_full_surface_center_taper = get_flm_full_surface_center_taper_scale(self.flm_surface_profile_key, setpoint, CS.vEgo,
|
||||
include_base_center=universal_flm_profile)
|
||||
ff *= get_flm_full_surface_ff_scale(self.flm_surface_profile_key, setpoint, desired_lateral_jerk, CS.vEgo,
|
||||
include_base_ff=universal_flm_profile) * flm_full_surface_center_taper
|
||||
friction_threshold = get_flm_full_surface_friction_threshold(self.flm_surface_profile_key, friction_threshold, CS.vEgo,
|
||||
setpoint, desired_lateral_jerk,
|
||||
include_base_threshold=universal_ftm_profile)
|
||||
include_base_threshold=universal_flm_profile)
|
||||
if trailer_load_kg > 0.0:
|
||||
ff *= get_trailer_lateral_ff_scale(trailer_load_kg, CS.vEgo, setpoint)
|
||||
friction_scale *= get_trailer_lateral_friction_scale(trailer_load_kg, CS.vEgo, setpoint)
|
||||
@@ -367,10 +367,10 @@ class LatControlTorque(LatControl):
|
||||
actual_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetDeg
|
||||
output_torque = get_ioniq_6_low_speed_angle_assist_torque(desired_angle_no_offset, actual_angle_no_offset,
|
||||
output_torque, CS.vEgo)
|
||||
elif ftm_surface_active and self.ftm_surface_profile_key and not CS.steeringPressed:
|
||||
elif flm_surface_active and self.flm_surface_profile_key and not CS.steeringPressed:
|
||||
desired_angle_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll))
|
||||
actual_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetDeg
|
||||
output_torque = get_ftm_full_surface_low_speed_angle_assist_torque(self.ftm_surface_profile_key, desired_angle_no_offset,
|
||||
output_torque = get_flm_full_surface_low_speed_angle_assist_torque(self.flm_surface_profile_key, desired_angle_no_offset,
|
||||
actual_angle_no_offset, output_torque, CS.vEgo)
|
||||
if ioniq_6_active:
|
||||
output_torque *= get_ioniq_6_highway_output_taper_scale(setpoint, CS.vEgo)
|
||||
|
||||
@@ -11,8 +11,8 @@ from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
CIVIC_BOSCH_MODIFIED_B_FIXED_FRICTION_THRESHOLD = 0.30
|
||||
STANDARD_FRICTION_THRESHOLD = 0.30
|
||||
HKG_CANFD_BASE_FRICTION_THRESHOLD = 0.39
|
||||
FTM_SCHEMA_VERSION = 1
|
||||
FTM_FRICTION_SPEED_KNOTS = [0.0, 5.0, 10.0, 15.0, 25.0]
|
||||
FLM_SCHEMA_VERSION = 1
|
||||
FLM_FRICTION_SPEED_KNOTS = [0.0, 5.0, 10.0, 15.0, 25.0]
|
||||
CIVIC_BOSCH_MODIFIED_B_LAT_ACCEL_FACTOR_MULT = 1.20
|
||||
CIVIC_BOSCH_MODIFIED_A_VARIANT_LAT_ACCEL_FACTOR_MULT = 1.00
|
||||
CIVIC_BOSCH_MODIFIED_B_VARIANT_LAT_ACCEL_FACTOR_MULT = 1.75
|
||||
@@ -695,8 +695,8 @@ TRAILER_LATERAL_LAT_RISE = 0.30
|
||||
TRAILER_LATERAL_FF_GAIN = 0.05
|
||||
TRAILER_LATERAL_FRICTION_GAIN = 0.03
|
||||
|
||||
_FTM_ACTIVE_OVERRIDES_TEXT = ""
|
||||
_FTM_ACTIVE_OVERRIDES = {}
|
||||
_FLM_ACTIVE_OVERRIDES_TEXT = ""
|
||||
_FLM_ACTIVE_OVERRIDES = {}
|
||||
|
||||
|
||||
def _sigmoid(x: float) -> float:
|
||||
@@ -720,11 +720,11 @@ def _hkg_canfd_base_friction_threshold_default(v_ego: float) -> float:
|
||||
return max(_gm_base_friction_threshold_default(v_ego), HKG_CANFD_BASE_FRICTION_THRESHOLD)
|
||||
|
||||
|
||||
def _ftm_copy_json(value):
|
||||
def _flm_copy_json(value):
|
||||
return json.loads(json.dumps(value))
|
||||
|
||||
|
||||
def normalize_ftm_overrides(overrides) -> dict:
|
||||
def normalize_flm_overrides(overrides) -> dict:
|
||||
if overrides in (None, "", b""):
|
||||
return {}
|
||||
|
||||
@@ -744,7 +744,7 @@ def normalize_ftm_overrides(overrides) -> dict:
|
||||
return {}
|
||||
|
||||
normalized = {
|
||||
"schemaVersion": FTM_SCHEMA_VERSION,
|
||||
"schemaVersion": FLM_SCHEMA_VERSION,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {},
|
||||
}
|
||||
@@ -752,11 +752,11 @@ def normalize_ftm_overrides(overrides) -> dict:
|
||||
for family in ("gm", "standard", "hkg_canfd"):
|
||||
payload = overrides.get("baseFrictionThresholds", {}).get(family, {})
|
||||
values = payload.get("values", payload if isinstance(payload, list) else [])
|
||||
if not isinstance(values, (list, tuple)) or len(values) != len(FTM_FRICTION_SPEED_KNOTS):
|
||||
if not isinstance(values, (list, tuple)) or len(values) != len(FLM_FRICTION_SPEED_KNOTS):
|
||||
continue
|
||||
try:
|
||||
normalized["baseFrictionThresholds"][family] = {
|
||||
"speedKnots": list(FTM_FRICTION_SPEED_KNOTS),
|
||||
"speedKnots": list(FLM_FRICTION_SPEED_KNOTS),
|
||||
"values": [float(v) for v in values],
|
||||
}
|
||||
except Exception:
|
||||
@@ -776,55 +776,55 @@ def normalize_ftm_overrides(overrides) -> dict:
|
||||
return normalized
|
||||
|
||||
|
||||
def set_ftm_runtime_overrides(overrides) -> None:
|
||||
global _FTM_ACTIVE_OVERRIDES, _FTM_ACTIVE_OVERRIDES_TEXT
|
||||
def set_flm_runtime_overrides(overrides) -> None:
|
||||
global _FLM_ACTIVE_OVERRIDES, _FLM_ACTIVE_OVERRIDES_TEXT
|
||||
|
||||
normalized = normalize_ftm_overrides(overrides)
|
||||
normalized = normalize_flm_overrides(overrides)
|
||||
text = json.dumps(normalized, sort_keys=True, separators=(",", ":")) if normalized else ""
|
||||
if text == _FTM_ACTIVE_OVERRIDES_TEXT:
|
||||
if text == _FLM_ACTIVE_OVERRIDES_TEXT:
|
||||
return
|
||||
|
||||
_FTM_ACTIVE_OVERRIDES_TEXT = text
|
||||
_FTM_ACTIVE_OVERRIDES = normalized
|
||||
_FLM_ACTIVE_OVERRIDES_TEXT = text
|
||||
_FLM_ACTIVE_OVERRIDES = normalized
|
||||
|
||||
|
||||
def clear_ftm_runtime_overrides() -> None:
|
||||
set_ftm_runtime_overrides({})
|
||||
def clear_flm_runtime_overrides() -> None:
|
||||
set_flm_runtime_overrides({})
|
||||
|
||||
|
||||
def get_ftm_runtime_overrides() -> dict:
|
||||
return _ftm_copy_json(_FTM_ACTIVE_OVERRIDES) if _FTM_ACTIVE_OVERRIDES else {}
|
||||
def get_flm_runtime_overrides() -> dict:
|
||||
return _flm_copy_json(_FLM_ACTIVE_OVERRIDES) if _FLM_ACTIVE_OVERRIDES else {}
|
||||
|
||||
|
||||
def ftm_runtime_overrides_active() -> bool:
|
||||
return bool(_FTM_ACTIVE_OVERRIDES)
|
||||
def flm_runtime_overrides_active() -> bool:
|
||||
return bool(_FLM_ACTIVE_OVERRIDES)
|
||||
|
||||
|
||||
def _ftm_base_friction_threshold(family: str, v_ego: float, default_fn) -> float:
|
||||
payload = _FTM_ACTIVE_OVERRIDES.get("baseFrictionThresholds", {}).get(family, {})
|
||||
def _flm_base_friction_threshold(family: str, v_ego: float, default_fn) -> float:
|
||||
payload = _FLM_ACTIVE_OVERRIDES.get("baseFrictionThresholds", {}).get(family, {})
|
||||
values = payload.get("values", [])
|
||||
if isinstance(values, list) and len(values) == len(FTM_FRICTION_SPEED_KNOTS):
|
||||
return float(np.interp(v_ego, FTM_FRICTION_SPEED_KNOTS, values))
|
||||
if isinstance(values, list) and len(values) == len(FLM_FRICTION_SPEED_KNOTS):
|
||||
return float(np.interp(v_ego, FLM_FRICTION_SPEED_KNOTS, values))
|
||||
return float(default_fn(v_ego))
|
||||
|
||||
|
||||
def _ftm_vehicle_knob(name: str, default_value: float) -> float:
|
||||
def _flm_vehicle_knob(name: str, default_value: float) -> float:
|
||||
try:
|
||||
return float(_FTM_ACTIVE_OVERRIDES.get("vehicleKnobs", {}).get(name, default_value))
|
||||
return float(_FLM_ACTIVE_OVERRIDES.get("vehicleKnobs", {}).get(name, default_value))
|
||||
except Exception:
|
||||
return float(default_value)
|
||||
|
||||
|
||||
def get_gm_base_friction_threshold(v_ego: float) -> float:
|
||||
return _ftm_base_friction_threshold("gm", v_ego, _gm_base_friction_threshold_default)
|
||||
return _flm_base_friction_threshold("gm", v_ego, _gm_base_friction_threshold_default)
|
||||
|
||||
|
||||
def get_standard_friction_threshold(v_ego: float) -> float:
|
||||
return _ftm_base_friction_threshold("standard", v_ego, _standard_friction_threshold_default)
|
||||
return _flm_base_friction_threshold("standard", v_ego, _standard_friction_threshold_default)
|
||||
|
||||
|
||||
def get_hkg_canfd_base_friction_threshold(v_ego: float) -> float:
|
||||
return _ftm_base_friction_threshold("hkg_canfd", v_ego, _hkg_canfd_base_friction_threshold_default)
|
||||
return _flm_base_friction_threshold("hkg_canfd", v_ego, _hkg_canfd_base_friction_threshold_default)
|
||||
|
||||
|
||||
def get_trailer_lateral_assist_factor(trailer_load_kg: float, v_ego: float, desired_lateral_accel: float) -> float:
|
||||
@@ -870,8 +870,8 @@ def get_prius_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float
|
||||
|
||||
gain = _prius_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("toyota_prius.ff_gain_left", PRIUS_FF_GAIN_LEFT),
|
||||
_ftm_vehicle_knob("toyota_prius.ff_gain_right", PRIUS_FF_GAIN_RIGHT),
|
||||
_flm_vehicle_knob("toyota_prius.ff_gain_left", PRIUS_FF_GAIN_LEFT),
|
||||
_flm_vehicle_knob("toyota_prius.ff_gain_right", PRIUS_FF_GAIN_RIGHT),
|
||||
)
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
onset = _prius_sigmoid((abs_lateral_accel - PRIUS_FF_ONSET) / PRIUS_FF_ONSET_WIDTH)
|
||||
@@ -883,14 +883,14 @@ def get_prius_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: float
|
||||
low_speed_factor = _prius_low_speed_factor(v_ego)
|
||||
turn_in_boost = 1.0 + (_prius_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("toyota_prius.turn_in_boost_left", PRIUS_TURN_IN_BOOST_LEFT),
|
||||
_ftm_vehicle_knob("toyota_prius.turn_in_boost_right", PRIUS_TURN_IN_BOOST_RIGHT),
|
||||
_flm_vehicle_knob("toyota_prius.turn_in_boost_left", PRIUS_TURN_IN_BOOST_LEFT),
|
||||
_flm_vehicle_knob("toyota_prius.turn_in_boost_right", PRIUS_TURN_IN_BOOST_RIGHT),
|
||||
) *
|
||||
turn_in_weight * (0.35 + 0.65 * low_speed_factor))
|
||||
unwind_taper = 1.0 - (_prius_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("toyota_prius.unwind_taper_left", PRIUS_UNWIND_TAPER_LEFT),
|
||||
_ftm_vehicle_knob("toyota_prius.unwind_taper_right", PRIUS_UNWIND_TAPER_RIGHT),
|
||||
_flm_vehicle_knob("toyota_prius.unwind_taper_left", PRIUS_UNWIND_TAPER_LEFT),
|
||||
_flm_vehicle_knob("toyota_prius.unwind_taper_right", PRIUS_UNWIND_TAPER_RIGHT),
|
||||
) *
|
||||
unwind_weight * (0.35 + 0.65 * low_speed_factor))
|
||||
return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0))
|
||||
@@ -904,14 +904,14 @@ def get_prius_friction_threshold(v_ego: float, desired_lateral_accel: float = 0.
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
threshold_scale = 1.0 - (_prius_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_left", PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_ftm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_right", PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
_flm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_left", PRIUS_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_flm_vehicle_knob("toyota_prius.turn_in_threshold_reduction_right", PRIUS_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
) *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_prius_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("toyota_prius.unwind_threshold_increase_left", PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_ftm_vehicle_knob("toyota_prius.unwind_threshold_increase_right", PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
_flm_vehicle_knob("toyota_prius.unwind_threshold_increase_left", PRIUS_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_flm_vehicle_knob("toyota_prius.unwind_threshold_increase_right", PRIUS_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
) *
|
||||
transition_envelope * unwind_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.86), 1.16)
|
||||
@@ -933,7 +933,7 @@ def get_prius_friction_scale(v_ego: float, desired_lateral_accel: float, desired
|
||||
def get_prius_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
speed_weight = _prius_sigmoid((v_ego - PRIUS_CENTER_TAPER_SPEED) / PRIUS_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _prius_sigmoid((PRIUS_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / PRIUS_CENTER_TAPER_LAT_WIDTH)
|
||||
reduction = _ftm_vehicle_knob("toyota_prius.center_taper_max", PRIUS_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
reduction = _flm_vehicle_knob("toyota_prius.center_taper_max", PRIUS_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
return 1.0 - reduction
|
||||
|
||||
|
||||
@@ -1241,8 +1241,8 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je
|
||||
|
||||
gain = _bolt_2022_2023_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.ff_gain_left", BOLT_2022_2023_FF_GAIN_LEFT),
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.ff_gain_right", BOLT_2022_2023_FF_GAIN_RIGHT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.ff_gain_left", BOLT_2022_2023_FF_GAIN_LEFT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.ff_gain_right", BOLT_2022_2023_FF_GAIN_RIGHT),
|
||||
)
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
onset = _bolt_2022_2023_sigmoid((abs_lateral_accel - BOLT_2022_2023_FF_ONSET) / BOLT_2022_2023_FF_ONSET_WIDTH)
|
||||
@@ -1250,7 +1250,7 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je
|
||||
extra_scale = gain * onset * cutoff
|
||||
speed_weight = _bolt_2022_2023_sigmoid((v_ego - BOLT_2022_2023_CENTER_TAPER_SPEED) / BOLT_2022_2023_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _bolt_2022_2023_sigmoid((BOLT_2022_2023_CENTER_TAPER_LAT - abs_lateral_accel) / BOLT_2022_2023_CENTER_TAPER_LAT_WIDTH)
|
||||
center_taper = 1.0 - (_ftm_vehicle_knob("gm_bolt_2022_2023.center_taper_max", BOLT_2022_2023_CENTER_TAPER_MAX) * speed_weight * center_weight)
|
||||
center_taper = 1.0 - (_flm_vehicle_knob("gm_bolt_2022_2023.center_taper_max", BOLT_2022_2023_CENTER_TAPER_MAX) * speed_weight * center_weight)
|
||||
low_speed_factor = _bolt_2022_2023_low_speed_factor(v_ego)
|
||||
transition_envelope = _bolt_2022_2023_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _bolt_2022_2023_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
@@ -1258,15 +1258,15 @@ def get_bolt_2022_2023_ff_scale(desired_lateral_accel: float, desired_lateral_je
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
turn_in_boost = 1.0 + (_bolt_2022_2023_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_left", BOLT_2022_2023_TURN_IN_BOOST_LEFT),
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_right", BOLT_2022_2023_TURN_IN_BOOST_RIGHT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_left", BOLT_2022_2023_TURN_IN_BOOST_LEFT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.turn_in_boost_right", BOLT_2022_2023_TURN_IN_BOOST_RIGHT),
|
||||
) *
|
||||
turn_in_weight * low_speed_factor)
|
||||
unwind_envelope = (0.25 + 0.75 * low_speed_factor) * (1.0 + 0.45 * transition_envelope)
|
||||
unwind_taper = 1.0 - (_bolt_2022_2023_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_left", BOLT_2022_2023_UNWIND_TAPER_LEFT),
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_right", BOLT_2022_2023_UNWIND_TAPER_RIGHT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_left", BOLT_2022_2023_UNWIND_TAPER_LEFT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.unwind_taper_right", BOLT_2022_2023_UNWIND_TAPER_RIGHT),
|
||||
) *
|
||||
unwind_weight * unwind_envelope)
|
||||
return 1.0 + (extra_scale * center_taper * turn_in_boost * max(unwind_taper, 0.0))
|
||||
@@ -1280,14 +1280,14 @@ def get_bolt_2022_2023_friction_threshold(v_ego: float, desired_lateral_accel: f
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
threshold_scale = 1.0 - (_bolt_2022_2023_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_left", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_right", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_left", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.turn_in_threshold_reduction_right", BOLT_2022_2023_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
) *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_bolt_2022_2023_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_left", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_ftm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_right", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_left", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_flm_vehicle_knob("gm_bolt_2022_2023.unwind_threshold_increase_right", BOLT_2022_2023_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
) *
|
||||
transition_envelope * unwind_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.84), 1.14)
|
||||
@@ -1954,16 +1954,16 @@ def _ioniq_6_transition_envelope(v_ego: float, desired_lateral_accel: float, des
|
||||
|
||||
|
||||
def _ioniq_6_curvy_speed_weight(v_ego: float) -> float:
|
||||
curvy_speed_min = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_speed_min", IONIQ_6_CURVY_SPEED_MIN)
|
||||
curvy_speed_max = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_speed_max", IONIQ_6_CURVY_SPEED_MAX)
|
||||
curvy_speed_min = _flm_vehicle_knob("hyundai_ioniq_6.curvy_speed_min", IONIQ_6_CURVY_SPEED_MIN)
|
||||
curvy_speed_max = _flm_vehicle_knob("hyundai_ioniq_6.curvy_speed_max", IONIQ_6_CURVY_SPEED_MAX)
|
||||
onset = _ioniq_6_sigmoid((max(v_ego, 0.0) - curvy_speed_min) / IONIQ_6_CURVY_SPEED_MIN_WIDTH)
|
||||
cutoff = _ioniq_6_sigmoid((curvy_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_SPEED_MAX_WIDTH)
|
||||
return onset * cutoff
|
||||
|
||||
|
||||
def _ioniq_6_curvy_turn_in_trim_speed_weight(v_ego: float) -> float:
|
||||
curvy_turn_in_speed_min = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_min", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN)
|
||||
curvy_turn_in_speed_max = _ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_max", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX)
|
||||
curvy_turn_in_speed_min = _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_min", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN)
|
||||
curvy_turn_in_speed_max = _flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_speed_max", IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX)
|
||||
onset = _ioniq_6_sigmoid((max(v_ego, 0.0) - curvy_turn_in_speed_min) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH)
|
||||
cutoff = _ioniq_6_sigmoid((curvy_turn_in_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH)
|
||||
return onset * cutoff
|
||||
@@ -1976,8 +1976,8 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo
|
||||
|
||||
gain = _ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.ff_gain_left", IONIQ_6_FF_GAIN_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.ff_gain_right", IONIQ_6_FF_GAIN_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.ff_gain_left", IONIQ_6_FF_GAIN_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.ff_gain_right", IONIQ_6_FF_GAIN_RIGHT),
|
||||
)
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
onset = _ioniq_6_sigmoid((abs_lateral_accel - IONIQ_6_FF_ONSET) / IONIQ_6_FF_ONSET_WIDTH)
|
||||
@@ -1989,14 +1989,14 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo
|
||||
low_speed_factor = _ioniq_6_low_speed_factor(v_ego)
|
||||
turn_in_boost = 1.0 + (_ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_left", IONIQ_6_TURN_IN_BOOST_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_right", IONIQ_6_TURN_IN_BOOST_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_left", IONIQ_6_TURN_IN_BOOST_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.turn_in_boost_right", IONIQ_6_TURN_IN_BOOST_RIGHT),
|
||||
) *
|
||||
turn_in_weight * low_speed_factor)
|
||||
unwind_taper = 1.0 - (_ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.unwind_taper_left", IONIQ_6_UNWIND_TAPER_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.unwind_taper_right", IONIQ_6_UNWIND_TAPER_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.unwind_taper_left", IONIQ_6_UNWIND_TAPER_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.unwind_taper_right", IONIQ_6_UNWIND_TAPER_RIGHT),
|
||||
) *
|
||||
unwind_weight * (0.30 + 0.70 * low_speed_factor))
|
||||
crawl_turn_in_scale = 0.0
|
||||
@@ -2007,8 +2007,8 @@ def get_ioniq_6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo
|
||||
IONIQ_6_CRAWL_TURN_IN_FF_LAT_WIDTH)
|
||||
crawl_turn_in_scale = _ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_left", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_right", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_left", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.crawl_turn_in_ff_boost_right", IONIQ_6_CRAWL_TURN_IN_FF_BOOST_RIGHT),
|
||||
) * crawl_speed_weight * crawl_lat_weight
|
||||
high_speed_right_turn_in_scale = 0.0
|
||||
if desired_lateral_accel < 0.0 and desired_lateral_accel * desired_lateral_jerk > 0.0:
|
||||
@@ -2034,14 +2034,14 @@ def get_ioniq_6_friction_threshold(v_ego: float, desired_lateral_accel: float =
|
||||
unwind_speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_UNWIND_HIGH_SPEED_SPEED) / IONIQ_6_UNWIND_HIGH_SPEED_SPEED_WIDTH)
|
||||
threshold_scale = 1.0 - (_ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_left", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_right", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_left", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.turn_in_threshold_reduction_right", IONIQ_6_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
) *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_ioniq_6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_left", IONIQ_6_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_right", IONIQ_6_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_left", IONIQ_6_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.unwind_threshold_increase_right", IONIQ_6_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
) *
|
||||
transition_envelope * unwind_weight * unwind_speed_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.82), 1.18)
|
||||
@@ -2070,12 +2070,12 @@ def get_ioniq_6_friction_center_fade_scale(desired_lateral_accel: float, v_ego:
|
||||
def get_ioniq_6_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_CENTER_TAPER_SPEED) / IONIQ_6_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _ioniq_6_sigmoid((IONIQ_6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_CENTER_TAPER_LAT_WIDTH)
|
||||
high_speed_reduction = _ftm_vehicle_knob("hyundai_ioniq_6.center_taper_max", IONIQ_6_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
high_speed_reduction = _flm_vehicle_knob("hyundai_ioniq_6.center_taper_max", IONIQ_6_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
|
||||
highway_speed_weight = _ioniq_6_sigmoid((v_ego - IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED) / IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED_WIDTH)
|
||||
highway_center_weight = _ioniq_6_sigmoid((IONIQ_6_HIGHWAY_CENTER_TAPER_LAT - abs(desired_lateral_accel)) /
|
||||
IONIQ_6_HIGHWAY_CENTER_TAPER_LAT_WIDTH)
|
||||
highway_center_reduction = _ftm_vehicle_knob("hyundai_ioniq_6.highway_center_taper_max", IONIQ_6_HIGHWAY_CENTER_TAPER_MAX) * highway_speed_weight * highway_center_weight
|
||||
highway_center_reduction = _flm_vehicle_knob("hyundai_ioniq_6.highway_center_taper_max", IONIQ_6_HIGHWAY_CENTER_TAPER_MAX) * highway_speed_weight * highway_center_weight
|
||||
|
||||
low_mid_onset = _ioniq_6_sigmoid((v_ego - IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_MIN) / IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_WIDTH)
|
||||
low_mid_cutoff = _ioniq_6_sigmoid((IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_MAX - v_ego) / IONIQ_6_LOW_MID_CENTER_TAPER_SPEED_WIDTH)
|
||||
@@ -2123,8 +2123,8 @@ def get_ioniq_6_directional_taper_scale(desired_lateral_accel: float, desired_la
|
||||
reduction = band_weight * (base_reduction + unwind_reduction * unwind_weight)
|
||||
reduction += heavy_band_weight * (heavy_base_reduction + heavy_unwind_reduction * unwind_weight)
|
||||
reduction += (_ioniq_6_side_value(desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_left", IONIQ_6_CURVY_TURN_IN_TRIM_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_right", IONIQ_6_CURVY_TURN_IN_TRIM_RIGHT)) *
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_left", IONIQ_6_CURVY_TURN_IN_TRIM_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_turn_in_trim_right", IONIQ_6_CURVY_TURN_IN_TRIM_RIGHT)) *
|
||||
curvy_turn_in_trim_weight)
|
||||
curvy_unwind_weight = 0.0
|
||||
curvy_unwind_floor_relief = 0.0
|
||||
@@ -2136,12 +2136,12 @@ def get_ioniq_6_directional_taper_scale(desired_lateral_accel: float, desired_la
|
||||
IONIQ_6_CURVY_UNWIND_LAT_CUTOFF_WIDTH)
|
||||
curvy_unwind_weight = curvy_unwind_speed_weight * curvy_unwind_lat_onset * curvy_unwind_lat_cutoff * unwind_weight
|
||||
curvy_unwind_floor_relief = (_ioniq_6_side_value(desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_left", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_right", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_RIGHT)) *
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_left", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_floor_relief_right", IONIQ_6_CURVY_UNWIND_FLOOR_RELIEF_RIGHT)) *
|
||||
curvy_unwind_weight)
|
||||
reduction += (_ioniq_6_side_value(desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_left", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_right", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_RIGHT)) *
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_left", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_LEFT),
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.curvy_unwind_extra_reduction_right", IONIQ_6_CURVY_UNWIND_EXTRA_REDUCTION_RIGHT)) *
|
||||
curvy_unwind_weight)
|
||||
floor = _ioniq_6_side_value(desired_lateral_accel, IONIQ_6_DIRECTIONAL_TAPER_FLOOR_LEFT, IONIQ_6_DIRECTIONAL_TAPER_FLOOR_RIGHT)
|
||||
floor -= _ioniq_6_side_value(desired_lateral_accel, IONIQ_6_DIRECTIONAL_TAPER_UNWIND_FLOOR_LEFT, IONIQ_6_DIRECTIONAL_TAPER_UNWIND_FLOOR_RIGHT) * unwind_weight
|
||||
@@ -2191,7 +2191,7 @@ def get_ioniq_6_low_speed_angle_assist_torque(desired_angle_deg: float, actual_a
|
||||
IONIQ_6_LOW_SPEED_ANGLE_ASSIST_TRACK_RATIO_WIDTH)
|
||||
tracking_scale = max(1.0 - tracking_taper, IONIQ_6_LOW_SPEED_ANGLE_ASSIST_TRACK_RATIO_FLOOR)
|
||||
assist_torque = math.copysign(
|
||||
_ftm_vehicle_knob("hyundai_ioniq_6.low_speed_angle_assist_max_torque", IONIQ_6_LOW_SPEED_ANGLE_ASSIST_MAX_TORQUE) *
|
||||
_flm_vehicle_knob("hyundai_ioniq_6.low_speed_angle_assist_max_torque", IONIQ_6_LOW_SPEED_ANGLE_ASSIST_MAX_TORQUE) *
|
||||
speed_weight * error_weight * desired_angle_weight * tracking_scale,
|
||||
-angle_error,
|
||||
)
|
||||
@@ -2254,8 +2254,8 @@ def get_kia_ev6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo
|
||||
|
||||
gain = _kia_ev6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.ff_gain_left", KIA_EV6_FF_GAIN_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.ff_gain_right", KIA_EV6_FF_GAIN_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.ff_gain_left", KIA_EV6_FF_GAIN_LEFT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.ff_gain_right", KIA_EV6_FF_GAIN_RIGHT),
|
||||
)
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
onset = _kia_ev6_sigmoid((abs_lateral_accel - KIA_EV6_FF_ONSET) / KIA_EV6_FF_ONSET_WIDTH)
|
||||
@@ -2267,14 +2267,14 @@ def get_kia_ev6_ff_scale(desired_lateral_accel: float, desired_lateral_jerk: flo
|
||||
low_speed_factor = _kia_ev6_low_speed_factor(v_ego)
|
||||
turn_in_boost = 1.0 + (_kia_ev6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_left", KIA_EV6_TURN_IN_BOOST_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_right", KIA_EV6_TURN_IN_BOOST_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_left", KIA_EV6_TURN_IN_BOOST_LEFT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.turn_in_boost_right", KIA_EV6_TURN_IN_BOOST_RIGHT),
|
||||
) *
|
||||
turn_in_weight * (0.35 + 0.65 * low_speed_factor))
|
||||
unwind_taper = 1.0 - (_kia_ev6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.unwind_taper_left", KIA_EV6_UNWIND_TAPER_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.unwind_taper_right", KIA_EV6_UNWIND_TAPER_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.unwind_taper_left", KIA_EV6_UNWIND_TAPER_LEFT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.unwind_taper_right", KIA_EV6_UNWIND_TAPER_RIGHT),
|
||||
) *
|
||||
unwind_weight * (0.35 + 0.65 * low_speed_factor))
|
||||
return 1.0 + (extra_scale * turn_in_boost * max(unwind_taper, 0.0))
|
||||
@@ -2288,14 +2288,14 @@ def get_kia_ev6_friction_threshold(v_ego: float, desired_lateral_accel: float =
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
threshold_scale = 1.0 - (_kia_ev6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_left", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_right", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_left", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_LEFT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.turn_in_threshold_reduction_right", KIA_EV6_TURN_IN_THRESHOLD_REDUCTION_RIGHT),
|
||||
) *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_kia_ev6_side_value(
|
||||
desired_lateral_accel,
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_left", KIA_EV6_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_ftm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_right", KIA_EV6_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_left", KIA_EV6_UNWIND_THRESHOLD_INCREASE_LEFT),
|
||||
_flm_vehicle_knob("hyundai_kia_ev6.unwind_threshold_increase_right", KIA_EV6_UNWIND_THRESHOLD_INCREASE_RIGHT),
|
||||
) *
|
||||
transition_envelope * unwind_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.82), 1.16)
|
||||
@@ -2317,7 +2317,7 @@ def get_kia_ev6_friction_scale(v_ego: float, desired_lateral_accel: float, desir
|
||||
def get_kia_ev6_center_taper_scale(desired_lateral_accel: float, v_ego: float) -> float:
|
||||
speed_weight = _kia_ev6_sigmoid((v_ego - KIA_EV6_CENTER_TAPER_SPEED) / KIA_EV6_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _kia_ev6_sigmoid((KIA_EV6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / KIA_EV6_CENTER_TAPER_LAT_WIDTH)
|
||||
reduction = _ftm_vehicle_knob("hyundai_kia_ev6.center_taper_max", KIA_EV6_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
reduction = _flm_vehicle_knob("hyundai_kia_ev6.center_taper_max", KIA_EV6_CENTER_TAPER_MAX) * speed_weight * center_weight
|
||||
return 1.0 - reduction
|
||||
|
||||
|
||||
@@ -2362,9 +2362,9 @@ def get_volt_plexy_center_taper_scale(desired_lateral_accel: float, v_ego: float
|
||||
return 1.0 - (standard_reduction * VOLT_PLEXY_CENTER_TAPER_REDUCTION_MULT)
|
||||
|
||||
|
||||
FTM_UNIVERSAL_PROFILE_KEY = "torque_universal"
|
||||
FLM_UNIVERSAL_PROFILE_KEY = "torque_universal"
|
||||
|
||||
FTM_FULL_SURFACE_SUFFIX_METADATA = {
|
||||
FLM_FULL_SURFACE_SUFFIX_METADATA = {
|
||||
"ff_gain_left": {"min": 0.0, "max": 0.60, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True},
|
||||
"ff_gain_right": {"min": 0.0, "max": 0.60, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True},
|
||||
"turn_in_boost_left": {"min": -0.10, "max": 2.80, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True},
|
||||
@@ -2392,7 +2392,7 @@ FTM_FULL_SURFACE_SUFFIX_METADATA = {
|
||||
"curvy_unwind_extra_reduction_right": {"min": 0.0, "max": 0.45, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True},
|
||||
}
|
||||
|
||||
FTM_FULL_SURFACE_NEUTRAL_DEFAULTS = {
|
||||
FLM_FULL_SURFACE_NEUTRAL_DEFAULTS = {
|
||||
"ff_gain_left": 0.0,
|
||||
"ff_gain_right": 0.0,
|
||||
"turn_in_boost_left": 0.0,
|
||||
@@ -2421,148 +2421,148 @@ FTM_FULL_SURFACE_NEUTRAL_DEFAULTS = {
|
||||
}
|
||||
|
||||
|
||||
def _ftm_profile_symbol(profile_key: str, suffix: str) -> str:
|
||||
def _flm_profile_symbol(profile_key: str, suffix: str) -> str:
|
||||
return f"{profile_key}.{suffix}"
|
||||
|
||||
|
||||
def ftm_profile_supports_knob(profile_key: str | None, suffix: str) -> bool:
|
||||
def flm_profile_supports_knob(profile_key: str | None, suffix: str) -> bool:
|
||||
if not profile_key:
|
||||
return False
|
||||
return _ftm_profile_symbol(profile_key, suffix) in FTM_SUPPORTED_VEHICLE_KNOBS
|
||||
return _flm_profile_symbol(profile_key, suffix) in FLM_SUPPORTED_VEHICLE_KNOBS
|
||||
|
||||
|
||||
def _ftm_full_surface_side_value(profile_key: str, desired_lateral_accel: float,
|
||||
def _flm_full_surface_side_value(profile_key: str, desired_lateral_accel: float,
|
||||
suffix: str, left_default: float = 0.0, right_default: float = 0.0) -> float:
|
||||
if desired_lateral_accel >= 0.0:
|
||||
return _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, f"{suffix}_left"), left_default)
|
||||
return _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, f"{suffix}_right"), right_default)
|
||||
return _flm_vehicle_knob(_flm_profile_symbol(profile_key, f"{suffix}_left"), left_default)
|
||||
return _flm_vehicle_knob(_flm_profile_symbol(profile_key, f"{suffix}_right"), right_default)
|
||||
|
||||
|
||||
def _ftm_full_surface_low_speed_factor(v_ego: float) -> float:
|
||||
def _flm_full_surface_low_speed_factor(v_ego: float) -> float:
|
||||
return 1.0 / (1.0 + (max(v_ego, 0.0) / IONIQ_6_TRANSITION_SPEED) ** 2)
|
||||
|
||||
|
||||
def _ftm_full_surface_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
def _flm_full_surface_transition_phase(desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
return math.tanh((desired_lateral_accel * desired_lateral_jerk) / IONIQ_6_PHASE_SCALE)
|
||||
|
||||
|
||||
def _ftm_full_surface_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
def _flm_full_surface_transition_envelope(v_ego: float, desired_lateral_accel: float, desired_lateral_jerk: float) -> float:
|
||||
lat_factor = 1.0 - math.exp(-abs(desired_lateral_accel) / IONIQ_6_FRICTION_LAT_RISE)
|
||||
jerk_factor = 1.0 - math.exp(-abs(desired_lateral_jerk) / IONIQ_6_FRICTION_JERK_RISE)
|
||||
return _ftm_full_surface_low_speed_factor(v_ego) * lat_factor * jerk_factor
|
||||
return _flm_full_surface_low_speed_factor(v_ego) * lat_factor * jerk_factor
|
||||
|
||||
|
||||
def _ftm_full_surface_curvy_speed_weight(profile_key: str, v_ego: float) -> float:
|
||||
curvy_speed_min = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_speed_min"), IONIQ_6_CURVY_SPEED_MIN)
|
||||
curvy_speed_max = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_speed_max"), IONIQ_6_CURVY_SPEED_MAX)
|
||||
def _flm_full_surface_curvy_speed_weight(profile_key: str, v_ego: float) -> float:
|
||||
curvy_speed_min = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_speed_min"), IONIQ_6_CURVY_SPEED_MIN)
|
||||
curvy_speed_max = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_speed_max"), IONIQ_6_CURVY_SPEED_MAX)
|
||||
onset = _sigmoid((max(v_ego, 0.0) - curvy_speed_min) / IONIQ_6_CURVY_SPEED_MIN_WIDTH)
|
||||
cutoff = _sigmoid((curvy_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_SPEED_MAX_WIDTH)
|
||||
return onset * cutoff
|
||||
|
||||
|
||||
def _ftm_full_surface_curvy_turn_in_trim_speed_weight(profile_key: str, v_ego: float) -> float:
|
||||
curvy_turn_in_speed_min = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_min"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN)
|
||||
curvy_turn_in_speed_max = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_max"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX)
|
||||
def _flm_full_surface_curvy_turn_in_trim_speed_weight(profile_key: str, v_ego: float) -> float:
|
||||
curvy_turn_in_speed_min = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_min"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MIN)
|
||||
curvy_turn_in_speed_max = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "curvy_turn_in_trim_speed_max"), IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_MAX)
|
||||
onset = _sigmoid((max(v_ego, 0.0) - curvy_turn_in_speed_min) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH)
|
||||
cutoff = _sigmoid((curvy_turn_in_speed_max - max(v_ego, 0.0)) / IONIQ_6_CURVY_TURN_IN_TRIM_SPEED_WIDTH)
|
||||
return onset * cutoff
|
||||
|
||||
|
||||
def get_ftm_full_surface_center_taper_scale(profile_key: str | None, desired_lateral_accel: float, v_ego: float,
|
||||
def get_flm_full_surface_center_taper_scale(profile_key: str | None, desired_lateral_accel: float, v_ego: float,
|
||||
include_base_center: bool = False) -> float:
|
||||
if not profile_key:
|
||||
return 1.0
|
||||
|
||||
reduction = 0.0
|
||||
if include_base_center and ftm_profile_supports_knob(profile_key, "center_taper_max"):
|
||||
if include_base_center and flm_profile_supports_knob(profile_key, "center_taper_max"):
|
||||
speed_weight = _sigmoid((v_ego - IONIQ_6_CENTER_TAPER_SPEED) / IONIQ_6_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _sigmoid((IONIQ_6_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_CENTER_TAPER_LAT_WIDTH)
|
||||
reduction += _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "center_taper_max"), 0.0) * speed_weight * center_weight
|
||||
reduction += _flm_vehicle_knob(_flm_profile_symbol(profile_key, "center_taper_max"), 0.0) * speed_weight * center_weight
|
||||
|
||||
if ftm_profile_supports_knob(profile_key, "highway_center_taper_max"):
|
||||
if flm_profile_supports_knob(profile_key, "highway_center_taper_max"):
|
||||
speed_weight = _sigmoid((v_ego - IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED) / IONIQ_6_HIGHWAY_CENTER_TAPER_SPEED_WIDTH)
|
||||
center_weight = _sigmoid((IONIQ_6_HIGHWAY_CENTER_TAPER_LAT - abs(desired_lateral_accel)) / IONIQ_6_HIGHWAY_CENTER_TAPER_LAT_WIDTH)
|
||||
reduction += _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "highway_center_taper_max"), 0.0) * speed_weight * center_weight
|
||||
reduction += _flm_vehicle_knob(_flm_profile_symbol(profile_key, "highway_center_taper_max"), 0.0) * speed_weight * center_weight
|
||||
|
||||
return 1.0 - min(reduction, 0.20)
|
||||
|
||||
|
||||
def get_ftm_full_surface_ff_scale(profile_key: str | None, desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float,
|
||||
def get_flm_full_surface_ff_scale(profile_key: str | None, desired_lateral_accel: float, desired_lateral_jerk: float, v_ego: float,
|
||||
include_base_ff: bool = False) -> float:
|
||||
if not profile_key or desired_lateral_accel == 0.0:
|
||||
return 1.0
|
||||
|
||||
abs_lateral_accel = abs(desired_lateral_accel)
|
||||
phase = _ftm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _flm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
low_speed_factor = _ftm_full_surface_low_speed_factor(v_ego)
|
||||
low_speed_factor = _flm_full_surface_low_speed_factor(v_ego)
|
||||
|
||||
curvy_turn_in_speed_weight = _ftm_full_surface_curvy_turn_in_trim_speed_weight(profile_key, v_ego)
|
||||
curvy_turn_in_speed_weight = _flm_full_surface_curvy_turn_in_trim_speed_weight(profile_key, v_ego)
|
||||
curvy_turn_in_lat_onset = _sigmoid((abs_lateral_accel - IONIQ_6_CURVY_TURN_IN_TRIM_LAT_START) / IONIQ_6_CURVY_TURN_IN_TRIM_LAT_ONSET_WIDTH)
|
||||
curvy_turn_in_lat_cutoff = _sigmoid((IONIQ_6_CURVY_TURN_IN_TRIM_LAT_END - abs_lateral_accel) / IONIQ_6_CURVY_TURN_IN_TRIM_LAT_CUTOFF_WIDTH)
|
||||
curvy_turn_in_trim_weight = curvy_turn_in_speed_weight * curvy_turn_in_lat_onset * curvy_turn_in_lat_cutoff * turn_in_weight
|
||||
|
||||
curvy_unwind_speed_weight = _ftm_full_surface_curvy_speed_weight(profile_key, v_ego)
|
||||
curvy_unwind_speed_weight = _flm_full_surface_curvy_speed_weight(profile_key, v_ego)
|
||||
curvy_unwind_lat_onset = _sigmoid((abs_lateral_accel - IONIQ_6_CURVY_UNWIND_LAT_START) / IONIQ_6_CURVY_UNWIND_LAT_ONSET_WIDTH)
|
||||
curvy_unwind_lat_cutoff = _sigmoid((IONIQ_6_CURVY_UNWIND_LAT_END - abs_lateral_accel) / IONIQ_6_CURVY_UNWIND_LAT_CUTOFF_WIDTH)
|
||||
curvy_unwind_weight = curvy_unwind_speed_weight * curvy_unwind_lat_onset * curvy_unwind_lat_cutoff * unwind_weight
|
||||
|
||||
scale = 1.0
|
||||
if include_base_ff and ftm_profile_supports_knob(profile_key, "ff_gain_left"):
|
||||
gain = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "ff_gain")
|
||||
if include_base_ff and flm_profile_supports_knob(profile_key, "ff_gain_left"):
|
||||
gain = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "ff_gain")
|
||||
onset = _sigmoid((abs_lateral_accel - IONIQ_6_FF_ONSET) / IONIQ_6_FF_ONSET_WIDTH)
|
||||
cutoff = _sigmoid((IONIQ_6_FF_CUTOFF - abs_lateral_accel) / IONIQ_6_FF_CUTOFF_WIDTH)
|
||||
extra_scale = gain * onset * cutoff
|
||||
turn_in_boost = 1.0 + (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_boost") * turn_in_weight * low_speed_factor)
|
||||
unwind_reduction = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_taper") * unwind_weight * (0.30 + 0.70 * low_speed_factor)
|
||||
curvy_unwind_extra = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight
|
||||
curvy_unwind_floor_relief = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight
|
||||
turn_in_boost = 1.0 + (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_boost") * turn_in_weight * low_speed_factor)
|
||||
unwind_reduction = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_taper") * unwind_weight * (0.30 + 0.70 * low_speed_factor)
|
||||
curvy_unwind_extra = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight
|
||||
curvy_unwind_floor_relief = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight
|
||||
unwind_floor = 1.0 - (0.55 * unwind_reduction) - curvy_unwind_floor_relief
|
||||
unwind_scale = max(1.0 - unwind_reduction - curvy_unwind_extra, unwind_floor, 0.0)
|
||||
scale *= 1.0 + (extra_scale * turn_in_boost * unwind_scale)
|
||||
else:
|
||||
curvy_unwind_extra = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight
|
||||
curvy_unwind_floor_relief = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight
|
||||
curvy_unwind_extra = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_extra_reduction") * curvy_unwind_weight
|
||||
curvy_unwind_floor_relief = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_unwind_floor_relief") * curvy_unwind_weight
|
||||
scale *= max(1.0 - curvy_unwind_extra - curvy_unwind_floor_relief, 0.55)
|
||||
|
||||
if ftm_profile_supports_knob(profile_key, "curvy_turn_in_trim_left"):
|
||||
curvy_trim = _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_turn_in_trim") * curvy_turn_in_trim_weight
|
||||
if flm_profile_supports_knob(profile_key, "curvy_turn_in_trim_left"):
|
||||
curvy_trim = _flm_full_surface_side_value(profile_key, desired_lateral_accel, "curvy_turn_in_trim") * curvy_turn_in_trim_weight
|
||||
scale *= max(1.0 - curvy_trim, 0.55)
|
||||
|
||||
if ftm_profile_supports_knob(profile_key, "crawl_turn_in_ff_boost_left") and desired_lateral_accel * desired_lateral_jerk > 0.0:
|
||||
if flm_profile_supports_knob(profile_key, "crawl_turn_in_ff_boost_left") and desired_lateral_accel * desired_lateral_jerk > 0.0:
|
||||
crawl_speed_weight = _sigmoid((IONIQ_6_CRAWL_TURN_IN_FF_SPEED - max(v_ego, 0.0)) / IONIQ_6_CRAWL_TURN_IN_FF_SPEED_WIDTH)
|
||||
crawl_lat_weight = _sigmoid((abs_lateral_accel - IONIQ_6_CRAWL_TURN_IN_FF_LAT) / IONIQ_6_CRAWL_TURN_IN_FF_LAT_WIDTH)
|
||||
scale += _ftm_full_surface_side_value(profile_key, desired_lateral_accel, "crawl_turn_in_ff_boost") * crawl_speed_weight * crawl_lat_weight
|
||||
scale += _flm_full_surface_side_value(profile_key, desired_lateral_accel, "crawl_turn_in_ff_boost") * crawl_speed_weight * crawl_lat_weight
|
||||
|
||||
return scale
|
||||
|
||||
|
||||
def get_ftm_full_surface_friction_threshold(profile_key: str | None, base_threshold: float, v_ego: float,
|
||||
def get_flm_full_surface_friction_threshold(profile_key: str | None, base_threshold: float, v_ego: float,
|
||||
desired_lateral_accel: float = 0.0, desired_lateral_jerk: float = 0.0,
|
||||
include_base_threshold: bool = False) -> float:
|
||||
if not profile_key or not include_base_threshold:
|
||||
return base_threshold
|
||||
|
||||
transition_envelope = _ftm_full_surface_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _ftm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
transition_envelope = _flm_full_surface_transition_envelope(v_ego, desired_lateral_accel, desired_lateral_jerk)
|
||||
phase = _flm_full_surface_transition_phase(desired_lateral_accel, desired_lateral_jerk)
|
||||
turn_in_weight = max(phase, 0.0)
|
||||
unwind_weight = max(-phase, 0.0)
|
||||
unwind_speed_weight = _sigmoid((v_ego - IONIQ_6_UNWIND_HIGH_SPEED_SPEED) / IONIQ_6_UNWIND_HIGH_SPEED_SPEED_WIDTH)
|
||||
threshold_scale = 1.0
|
||||
threshold_scale -= (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_threshold_reduction") *
|
||||
threshold_scale -= (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "turn_in_threshold_reduction") *
|
||||
transition_envelope * turn_in_weight)
|
||||
threshold_scale += (_ftm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_threshold_increase") *
|
||||
threshold_scale += (_flm_full_surface_side_value(profile_key, desired_lateral_accel, "unwind_threshold_increase") *
|
||||
transition_envelope * unwind_weight * unwind_speed_weight)
|
||||
return base_threshold * min(max(threshold_scale, 0.82), 1.18)
|
||||
|
||||
|
||||
def get_ftm_full_surface_low_speed_angle_assist_torque(profile_key: str | None, desired_angle_deg: float, actual_angle_deg: float,
|
||||
def get_flm_full_surface_low_speed_angle_assist_torque(profile_key: str | None, desired_angle_deg: float, actual_angle_deg: float,
|
||||
current_output_torque: float, v_ego: float) -> float:
|
||||
if not profile_key or not ftm_profile_supports_knob(profile_key, "low_speed_angle_assist_max_torque"):
|
||||
if not profile_key or not flm_profile_supports_knob(profile_key, "low_speed_angle_assist_max_torque"):
|
||||
return current_output_torque
|
||||
|
||||
max_torque = _ftm_vehicle_knob(_ftm_profile_symbol(profile_key, "low_speed_angle_assist_max_torque"), 0.0)
|
||||
max_torque = _flm_vehicle_knob(_flm_profile_symbol(profile_key, "low_speed_angle_assist_max_torque"), 0.0)
|
||||
if max_torque <= 1e-4:
|
||||
return current_output_torque
|
||||
|
||||
@@ -2593,22 +2593,22 @@ def get_ftm_full_surface_low_speed_angle_assist_torque(profile_key: str | None,
|
||||
return float(np.clip(current_output_torque + assist_torque, -1.0, 1.0))
|
||||
|
||||
|
||||
FTM_RICH_PROFILE_CARS = {
|
||||
FLM_RICH_PROFILE_CARS = {
|
||||
"gm_bolt_2022_2023": set(BOLT_2022_2023_CARS),
|
||||
"hyundai_ioniq_6": set(IONIQ_6_CARS),
|
||||
"hyundai_kia_ev6": set(KIA_EV6_CARS),
|
||||
"toyota_prius": set(PRIUS_CARS),
|
||||
}
|
||||
|
||||
FTM_RICH_PROFILE_LABELS = {
|
||||
FLM_RICH_PROFILE_LABELS = {
|
||||
"gm_bolt_2022_2023": "Bolt 2022-2023",
|
||||
"hyundai_ioniq_6": "Ioniq 6",
|
||||
"hyundai_kia_ev6": "EV6",
|
||||
"toyota_prius": "Prius",
|
||||
FTM_UNIVERSAL_PROFILE_KEY: "Torque Controller",
|
||||
FLM_UNIVERSAL_PROFILE_KEY: "Torque Controller",
|
||||
}
|
||||
|
||||
FTM_SUPPORTED_VEHICLE_KNOBS = {
|
||||
FLM_SUPPORTED_VEHICLE_KNOBS = {
|
||||
"gm_bolt_2022_2023.ff_gain_left": {"profile": "gm_bolt_2022_2023", "min": 0.0, "max": 0.40, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_FF_GAIN_LEFT},
|
||||
"gm_bolt_2022_2023.ff_gain_right": {"profile": "gm_bolt_2022_2023", "min": 0.0, "max": 0.40, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_FF_GAIN_RIGHT},
|
||||
"gm_bolt_2022_2023.turn_in_boost_left": {"profile": "gm_bolt_2022_2023", "min": -0.10, "max": 0.50, "precision": 0.001, "deltaType": "absolute", "safeLiveTrial": True, "defaultValue": BOLT_2022_2023_TURN_IN_BOOST_LEFT},
|
||||
@@ -2670,16 +2670,16 @@ FTM_SUPPORTED_VEHICLE_KNOBS = {
|
||||
}
|
||||
|
||||
|
||||
def _add_ftm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, float] | None = None) -> None:
|
||||
knob_defaults = dict(FTM_FULL_SURFACE_NEUTRAL_DEFAULTS)
|
||||
def _add_flm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, float] | None = None) -> None:
|
||||
knob_defaults = dict(FLM_FULL_SURFACE_NEUTRAL_DEFAULTS)
|
||||
if defaults:
|
||||
knob_defaults.update(defaults)
|
||||
|
||||
for suffix, meta in FTM_FULL_SURFACE_SUFFIX_METADATA.items():
|
||||
symbol = _ftm_profile_symbol(profile_key, suffix)
|
||||
if symbol in FTM_SUPPORTED_VEHICLE_KNOBS:
|
||||
for suffix, meta in FLM_FULL_SURFACE_SUFFIX_METADATA.items():
|
||||
symbol = _flm_profile_symbol(profile_key, suffix)
|
||||
if symbol in FLM_SUPPORTED_VEHICLE_KNOBS:
|
||||
continue
|
||||
FTM_SUPPORTED_VEHICLE_KNOBS[symbol] = {
|
||||
FLM_SUPPORTED_VEHICLE_KNOBS[symbol] = {
|
||||
"profile": profile_key,
|
||||
"min": meta["min"],
|
||||
"max": meta["max"],
|
||||
@@ -2690,30 +2690,30 @@ def _add_ftm_full_surface_profile_knobs(profile_key: str, defaults: dict[str, fl
|
||||
}
|
||||
|
||||
|
||||
for _ftm_profile_key in ("gm_bolt_2022_2023", "hyundai_ioniq_6", "hyundai_kia_ev6", "toyota_prius", FTM_UNIVERSAL_PROFILE_KEY):
|
||||
_add_ftm_full_surface_profile_knobs(_ftm_profile_key)
|
||||
for _flm_profile_key in ("gm_bolt_2022_2023", "hyundai_ioniq_6", "hyundai_kia_ev6", "toyota_prius", FLM_UNIVERSAL_PROFILE_KEY):
|
||||
_add_flm_full_surface_profile_knobs(_flm_profile_key)
|
||||
|
||||
|
||||
def get_ftm_supported_vehicle_knobs() -> dict:
|
||||
return _ftm_copy_json(FTM_SUPPORTED_VEHICLE_KNOBS)
|
||||
def get_flm_supported_vehicle_knobs() -> dict:
|
||||
return _flm_copy_json(FLM_SUPPORTED_VEHICLE_KNOBS)
|
||||
|
||||
|
||||
def get_ftm_rich_profile_key(car_fingerprint) -> str | None:
|
||||
for profile_key, cars in FTM_RICH_PROFILE_CARS.items():
|
||||
def get_flm_rich_profile_key(car_fingerprint) -> str | None:
|
||||
for profile_key, cars in FLM_RICH_PROFILE_CARS.items():
|
||||
if car_fingerprint in cars:
|
||||
return profile_key
|
||||
return None
|
||||
|
||||
|
||||
def get_ftm_surface_profile_key(car_fingerprint, torque_control: bool = True) -> str | None:
|
||||
profile_key = get_ftm_rich_profile_key(car_fingerprint)
|
||||
def get_flm_surface_profile_key(car_fingerprint, torque_control: bool = True) -> str | None:
|
||||
profile_key = get_flm_rich_profile_key(car_fingerprint)
|
||||
if profile_key is not None or not torque_control:
|
||||
return profile_key
|
||||
return FTM_UNIVERSAL_PROFILE_KEY
|
||||
return FLM_UNIVERSAL_PROFILE_KEY
|
||||
|
||||
|
||||
def get_ftm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool = False, torque_control: bool = True) -> dict:
|
||||
profile_key = get_ftm_surface_profile_key(car_fingerprint, torque_control=torque_control)
|
||||
def get_flm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool = False, torque_control: bool = True) -> dict:
|
||||
profile_key = get_flm_surface_profile_key(car_fingerprint, torque_control=torque_control)
|
||||
if hyundai_canfd:
|
||||
friction_family = "hkg_canfd"
|
||||
elif brand == "gm":
|
||||
@@ -2732,14 +2732,14 @@ def get_ftm_capabilities(car_fingerprint, brand: str = "", hyundai_canfd: bool =
|
||||
set(KIA_XCEED_CARS) | set(KIA_NIRO_PHEV_2022_CARS) | set(KIA_FORTE_CARS) | set(KIA_EV6_CARS) |
|
||||
set(SILVERADO_CARS)
|
||||
)
|
||||
rich_knobs = [name for name, meta in FTM_SUPPORTED_VEHICLE_KNOBS.items() if meta["profile"] == profile_key]
|
||||
rich_knobs = [name for name, meta in FLM_SUPPORTED_VEHICLE_KNOBS.items() if meta["profile"] == profile_key]
|
||||
return {
|
||||
"torqueControl": bool(torque_control),
|
||||
"frictionFamily": friction_family,
|
||||
"hasDedicatedFrictionThreshold": bool(dedicated_friction),
|
||||
"hasDedicatedCenterTaper": bool(dedicated_center_taper),
|
||||
"richProfileKey": profile_key,
|
||||
"richProfileLabel": FTM_RICH_PROFILE_LABELS.get(profile_key),
|
||||
"richProfileLabel": FLM_RICH_PROFILE_LABELS.get(profile_key),
|
||||
"richKnobs": rich_knobs,
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import (
|
||||
get_civic_bosch_modified_pid_output_scale,
|
||||
)
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes import (
|
||||
clear_ftm_runtime_overrides,
|
||||
get_ftm_runtime_overrides,
|
||||
clear_flm_runtime_overrides,
|
||||
get_flm_runtime_overrides,
|
||||
get_hkg_canfd_base_friction_threshold,
|
||||
normalize_ftm_overrides,
|
||||
set_ftm_runtime_overrides,
|
||||
normalize_flm_overrides,
|
||||
set_flm_runtime_overrides,
|
||||
)
|
||||
from openpilot.selfdrive.controls.lib.latcontrol_torque import (
|
||||
get_civic_bosch_modified_a_center_taper_scale,
|
||||
@@ -264,9 +264,9 @@ class TestLatControl:
|
||||
assert unwind_left < steady_left
|
||||
assert unwind_right < steady_right
|
||||
|
||||
def test_ftm_standard_friction_curve_override(self):
|
||||
def test_flm_standard_friction_curve_override(self):
|
||||
base = get_standard_friction_threshold(10.0)
|
||||
overrides = normalize_ftm_overrides({
|
||||
overrides = normalize_flm_overrides({
|
||||
"baseFrictionThresholds": {
|
||||
"standard": {
|
||||
"values": [0.30, 0.31, 0.32, 0.33, 0.34],
|
||||
@@ -274,54 +274,54 @@ class TestLatControl:
|
||||
},
|
||||
})
|
||||
try:
|
||||
set_ftm_runtime_overrides(overrides)
|
||||
assert get_ftm_runtime_overrides()["baseFrictionThresholds"]["standard"]["values"] == [0.30, 0.31, 0.32, 0.33, 0.34]
|
||||
set_flm_runtime_overrides(overrides)
|
||||
assert get_flm_runtime_overrides()["baseFrictionThresholds"]["standard"]["values"] == [0.30, 0.31, 0.32, 0.33, 0.34]
|
||||
assert get_standard_friction_threshold(10.0) == pytest.approx(0.32)
|
||||
assert get_standard_friction_threshold(12.5) > get_standard_friction_threshold(10.0)
|
||||
assert get_standard_friction_threshold(10.0) != pytest.approx(base)
|
||||
finally:
|
||||
clear_ftm_runtime_overrides()
|
||||
assert get_ftm_runtime_overrides() == {}
|
||||
clear_flm_runtime_overrides()
|
||||
assert get_flm_runtime_overrides() == {}
|
||||
assert get_standard_friction_threshold(10.0) == pytest.approx(base)
|
||||
|
||||
def test_ftm_vehicle_knob_override_ioniq6_center_taper(self):
|
||||
def test_flm_vehicle_knob_override_ioniq6_center_taper(self):
|
||||
baseline = get_ioniq_6_center_taper_scale(0.0, 32.0)
|
||||
overrides = normalize_ftm_overrides({
|
||||
overrides = normalize_flm_overrides({
|
||||
"vehicleKnobs": {
|
||||
"hyundai_ioniq_6.center_taper_max": 0.0,
|
||||
"hyundai_ioniq_6.highway_center_taper_max": 0.0,
|
||||
},
|
||||
})
|
||||
try:
|
||||
set_ftm_runtime_overrides(overrides)
|
||||
set_flm_runtime_overrides(overrides)
|
||||
adjusted = get_ioniq_6_center_taper_scale(0.0, 32.0)
|
||||
assert adjusted > baseline
|
||||
assert adjusted <= 1.0
|
||||
finally:
|
||||
clear_ftm_runtime_overrides()
|
||||
clear_flm_runtime_overrides()
|
||||
|
||||
@pytest.mark.parametrize(("trial_applied", "profile_id"), [(False, "profile"), (True, "")])
|
||||
def test_ftm_surface_helpers_require_active_trial(self, monkeypatch, trial_applied, profile_id):
|
||||
def test_flm_surface_helpers_require_active_trial(self, monkeypatch, trial_applied, profile_id):
|
||||
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023)
|
||||
starpilot_toggles.ftm_trial_applied = trial_applied
|
||||
starpilot_toggles.ftm_active_profile_id = profile_id
|
||||
starpilot_toggles.ftm_active_overrides = {
|
||||
starpilot_toggles.flm_trial_applied = trial_applied
|
||||
starpilot_toggles.flm_active_profile_id = profile_id
|
||||
starpilot_toggles.flm_active_overrides = {
|
||||
"vehicleKnobs": {"gm_bolt_2022_2023.highway_center_taper_max": 0.10},
|
||||
}
|
||||
|
||||
def fail_if_called(*_args, **_kwargs):
|
||||
raise AssertionError("inactive FTM trial reached the runtime shaping path")
|
||||
raise AssertionError("inactive FLM trial reached the runtime shaping path")
|
||||
|
||||
monkeypatch.setattr(latcontrol_torque, "get_ftm_full_surface_ff_scale", fail_if_called)
|
||||
monkeypatch.setattr(latcontrol_torque, "get_flm_full_surface_ff_scale", fail_if_called)
|
||||
controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
|
||||
assert get_ftm_runtime_overrides() == {}
|
||||
assert get_flm_runtime_overrides() == {}
|
||||
|
||||
def test_ftm_surface_helpers_run_for_active_trial(self, monkeypatch):
|
||||
def test_flm_surface_helpers_run_for_active_trial(self, monkeypatch):
|
||||
controller, VM, CS, params, starpilot_toggles = self._build_torque_controller(GM.CHEVROLET_BOLT_ACC_2022_2023)
|
||||
starpilot_toggles.ftm_trial_applied = True
|
||||
starpilot_toggles.ftm_active_profile_id = "report:cleanup:recommended"
|
||||
starpilot_toggles.ftm_active_overrides = {
|
||||
starpilot_toggles.flm_trial_applied = True
|
||||
starpilot_toggles.flm_active_profile_id = "report:cleanup:recommended"
|
||||
starpilot_toggles.flm_active_overrides = {
|
||||
"vehicleKnobs": {"gm_bolt_2022_2023.highway_center_taper_max": 0.10},
|
||||
}
|
||||
calls = []
|
||||
@@ -330,13 +330,13 @@ class TestLatControl:
|
||||
calls.append(True)
|
||||
return 1.0
|
||||
|
||||
monkeypatch.setattr(latcontrol_torque, "get_ftm_full_surface_ff_scale", record_call)
|
||||
monkeypatch.setattr(latcontrol_torque, "get_flm_full_surface_ff_scale", record_call)
|
||||
try:
|
||||
controller.update(True, CS, VM, params, False, 0.0025, False, 0.2, None, None, starpilot_toggles)
|
||||
assert calls
|
||||
assert get_ftm_runtime_overrides()["vehicleKnobs"]["gm_bolt_2022_2023.highway_center_taper_max"] == pytest.approx(0.10)
|
||||
assert get_flm_runtime_overrides()["vehicleKnobs"]["gm_bolt_2022_2023.highway_center_taper_max"] == pytest.approx(0.10)
|
||||
finally:
|
||||
clear_ftm_runtime_overrides()
|
||||
clear_flm_runtime_overrides()
|
||||
|
||||
def test_sonata_hybrid_center_taper_curve(self):
|
||||
assert get_sonata_hybrid_center_taper_scale(0.0, 30.0) < get_sonata_hybrid_center_taper_scale(0.0, 15.0)
|
||||
|
||||
@@ -14,7 +14,7 @@ METRIC_MARGIN = 12
|
||||
FONT_SIZE = 35
|
||||
METER_TO_FOOT = 3.28084
|
||||
_WHITE_DIM = rl.Color(255, 255, 255, 85)
|
||||
_FTM_OVERRIDE_COLOR = rl.Color(239, 68, 68, 255)
|
||||
_FLM_OVERRIDE_COLOR = rl.Color(239, 68, 68, 255)
|
||||
|
||||
def parse_hex_color(hex_str: str, default_color=rl.WHITE) -> rl.Color:
|
||||
if not hex_str:
|
||||
@@ -56,7 +56,7 @@ class DeveloperSidebar:
|
||||
self._cached_metrics = [0] * 7
|
||||
self._cached_force_auto_tune_off = False
|
||||
self._cached_force_auto_tune = False
|
||||
self._cached_ftm_trial_applied = False
|
||||
self._cached_flm_trial_applied = False
|
||||
self._cached_friction_stock = 0.0
|
||||
self._cached_friction = 0.0
|
||||
self._cached_lat_stock = 0.0
|
||||
@@ -73,7 +73,7 @@ class DeveloperSidebar:
|
||||
self._visible = False
|
||||
self._metric_color = rl.WHITE
|
||||
self._active_ids: list[int] = []
|
||||
self._ftm_override_metric_ids: set[int] = set()
|
||||
self._flm_override_metric_ids: set[int] = set()
|
||||
self._metrics: dict[int, tuple[str, str]] = {}
|
||||
|
||||
@property
|
||||
@@ -97,7 +97,7 @@ class DeveloperSidebar:
|
||||
self._cached_metrics = [self._params.get_int(f"DeveloperSidebarMetric{i}") for i in range(1, 8)]
|
||||
self._cached_force_auto_tune_off = self._params.get_bool("ForceAutoTuneOff")
|
||||
self._cached_force_auto_tune = self._params.get_bool("ForceAutoTune")
|
||||
self._cached_ftm_trial_applied = self._params.get_bool("FTMTrialApplied")
|
||||
self._cached_flm_trial_applied = self._params.get_bool("FLMTrialApplied")
|
||||
self._cached_friction_stock = self._params.get_float("SteerFrictionStock")
|
||||
self._cached_friction = self._params.get_float("SteerFriction")
|
||||
self._cached_lat_stock = self._params.get_float("SteerLatAccelStock")
|
||||
@@ -222,15 +222,15 @@ class DeveloperSidebar:
|
||||
use_custom_friction = bool(ui_state.starpilot_toggles.get("use_custom_friction", force_auto_tune_off))
|
||||
use_custom_lat_factor = bool(ui_state.starpilot_toggles.get("use_custom_latAccelFactor", force_auto_tune_off))
|
||||
|
||||
self._ftm_override_metric_ids = set()
|
||||
if self._cached_ftm_trial_applied:
|
||||
ftm_metric_flags = {
|
||||
self._flm_override_metric_ids = set()
|
||||
if self._cached_flm_trial_applied:
|
||||
flm_metric_flags = {
|
||||
3: bool(ui_state.starpilot_toggles.get("use_custom_steerActuatorDelay", False)),
|
||||
4: use_custom_friction,
|
||||
5: use_custom_lat_factor,
|
||||
6: bool(ui_state.starpilot_toggles.get("use_custom_steerRatio", False)),
|
||||
}
|
||||
self._ftm_override_metric_ids = {metric_id for metric_id, enabled in ftm_metric_flags.items() if enabled}
|
||||
self._flm_override_metric_ids = {metric_id for metric_id, enabled in flm_metric_flags.items() if enabled}
|
||||
|
||||
friction_coeff = resolve_effective_torque_value(
|
||||
use_custom_friction,
|
||||
@@ -305,6 +305,6 @@ class DeveloperSidebar:
|
||||
if metric_id <= 0 or metric_id not in self._metrics:
|
||||
continue
|
||||
label_first, label_second = self._metrics[metric_id]
|
||||
ftm_overridden = metric_id in self._ftm_override_metric_ids
|
||||
self._draw_metric(sidebar_rect, label_first, label_second, _FTM_OVERRIDE_COLOR if ftm_overridden else self._metric_color, y)
|
||||
flm_overridden = metric_id in self._flm_override_metric_ids
|
||||
self._draw_metric(sidebar_rect, label_first, label_second, _FLM_OVERRIDE_COLOR if flm_overridden else self._metric_color, y)
|
||||
y += METRIC_HEIGHT + spacing
|
||||
|
||||
@@ -232,10 +232,10 @@ EXCLUDED_KEYS = {
|
||||
"CommunityFavorites",
|
||||
"CurvatureData",
|
||||
"ExperimentalLongitudinalEnabled",
|
||||
"FTMActiveOverrides",
|
||||
"FTMActiveProfileId",
|
||||
"FTMTrialBaseline",
|
||||
"FTMTrialApplied",
|
||||
"FLMActiveOverrides",
|
||||
"FLMActiveProfileId",
|
||||
"FLMTrialBaseline",
|
||||
"FLMTrialApplied",
|
||||
"InstallDate",
|
||||
"StarPilotCarParamsPersistent",
|
||||
"KonikMinutes",
|
||||
@@ -708,13 +708,13 @@ class StarPilotVariables:
|
||||
advanced_lateral_tuning = self.get_value("AdvancedLateralTune")
|
||||
toggle.force_auto_tune = self.get_value("ForceAutoTune", condition=advanced_lateral_tuning and not has_auto_tune and is_torque_car and not is_angle_car)
|
||||
toggle.force_auto_tune_off = self.get_value("ForceAutoTuneOff", condition=advanced_lateral_tuning and has_auto_tune and is_torque_car and not is_angle_car)
|
||||
toggle.ftm_active_profile_id = self.params.get("FTMActiveProfileId", encoding="utf-8") or ""
|
||||
toggle.ftm_trial_applied = self.params.get_bool("FTMTrialApplied")
|
||||
ftm_overrides_raw = self.params.get("FTMActiveOverrides", encoding="utf-8") or ""
|
||||
toggle.flm_active_profile_id = self.params.get("FLMActiveProfileId", encoding="utf-8") or ""
|
||||
toggle.flm_trial_applied = self.params.get_bool("FLMTrialApplied")
|
||||
flm_overrides_raw = self.params.get("FLMActiveOverrides", encoding="utf-8") or ""
|
||||
try:
|
||||
toggle.ftm_active_overrides = json.loads(ftm_overrides_raw) if ftm_overrides_raw else {}
|
||||
toggle.flm_active_overrides = json.loads(flm_overrides_raw) if flm_overrides_raw else {}
|
||||
except Exception:
|
||||
toggle.ftm_active_overrides = {}
|
||||
toggle.flm_active_overrides = {}
|
||||
toggle.use_auto_steer_delay = self.get_value("UseAutoSteerDelay", condition=advanced_lateral_tuning, default=True)
|
||||
toggle.steerActuatorDelay = self.get_value("SteerDelay", cast=float, condition=advanced_lateral_tuning, default=fullSteerActuatorDelay, min=0.01, max=1.0)
|
||||
toggle.use_custom_steerActuatorDelay = advanced_lateral_tuning and not toggle.use_auto_steer_delay
|
||||
|
||||
@@ -21,7 +21,7 @@ RUNTIME_LOOP_HZ = 20
|
||||
INFERENCE_INTERVAL = 0.15
|
||||
FOLLOWUP_INFERENCE_INTERVAL = 0.10
|
||||
FOLLOWUP_WINDOW_SECONDS = 2.0
|
||||
TEMPORAL_TRACKING_ENABLED = True
|
||||
TEMPORAL_TRACKING_ENABLED = False
|
||||
TRACK_CONFIRMED_PROPOSALS_ENABLED = False
|
||||
TRACK_CLASSIFICATION_INTERVAL = 0.12
|
||||
TRACK_BUSY_CLASSIFICATION_INTERVAL = 0.35
|
||||
@@ -58,6 +58,7 @@ CONSISTENT_DETECTIONS = 2
|
||||
# These counts must remain achievable at the measured 1.5 Hz onroad cadence.
|
||||
CHANGE_CONSISTENT_DETECTIONS = 2
|
||||
CHANGE_SINGLE_READ_MIN_CONFIDENCE = 0.83
|
||||
CHANGE_REPEAT_MIN_CONFIDENCE = 0.70
|
||||
LOW_SPEED_CHANGE_CONSISTENT_DETECTIONS = 2
|
||||
LOW_SPEED_CHANGE_MIN_CONFIDENCE = 0.90
|
||||
LOW_SPEED_CHANGE_ALLOW_STRONG_CONSENSUS = True
|
||||
@@ -2150,6 +2151,7 @@ class SpeedLimitVisionDaemon:
|
||||
counts = Counter(entry.speed_limit_mph for entry in self.history)
|
||||
candidate_speed_limit, candidate_count = counts.most_common(1)[0]
|
||||
matching_entries = [entry for entry in self.history if entry.speed_limit_mph == candidate_speed_limit]
|
||||
matching_confidences = sorted((entry.confidence for entry in matching_entries), reverse=True)
|
||||
best_confidence = max(entry.confidence for entry in matching_entries)
|
||||
has_strong_consensus = any(entry.strong_consensus for entry in matching_entries)
|
||||
current_speed_limit = self.published_speed_limit_mph
|
||||
@@ -2170,6 +2172,11 @@ class SpeedLimitVisionDaemon:
|
||||
return None
|
||||
if candidate_count < required_count and not allow_single_frame_confirmation:
|
||||
return None
|
||||
if (
|
||||
not allow_single_frame_confirmation and
|
||||
matching_confidences[required_count - 1] < CHANGE_REPEAT_MIN_CONFIDENCE
|
||||
):
|
||||
return None
|
||||
if candidate_count <= current_count:
|
||||
return None
|
||||
return candidate_speed_limit, best_confidence
|
||||
|
||||
@@ -22,6 +22,11 @@ def test_speed_change_requires_two_matching_reads_below_single_read_threshold():
|
||||
assert daemon._confirm_detection() == pytest.approx((55, 0.82))
|
||||
|
||||
|
||||
def test_speed_change_rejects_weak_confirming_read():
|
||||
daemon = daemon_with_history(40, [(35, 0.78), (35, 0.48)])
|
||||
assert daemon._confirm_detection() is None
|
||||
|
||||
|
||||
def test_speed_change_accepts_single_high_confidence_read():
|
||||
daemon = daemon_with_history(40, [(55, 0.84)])
|
||||
assert daemon._confirm_detection() == pytest.approx((55, 0.84))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { html, reactive } from "/assets/vendor/arrow-core.js"
|
||||
import { createBrowserHistory, createRouter } from "/assets/vendor/remix-router-1.3.1.js"
|
||||
import { hideSidebar } from "/assets/js/utils.js"
|
||||
import { DeviceSettings } from "/assets/components/tools/device_settings.js?v=ftm-overrides-1"
|
||||
import { DeviceSettings } from "/assets/components/tools/device_settings.js?v=flm-overrides-1"
|
||||
import { ErrorLogs } from "/assets/components/tools/error_logs.js"
|
||||
import { VehicleFeatures } from "/assets/components/tools/vehicle_features.js"
|
||||
import { GalaxyPairing } from "/assets/components/tools/galaxy.js"
|
||||
@@ -19,7 +19,7 @@ import { ModelManager } from "/assets/components/tools/model_manager.js?v=202603
|
||||
import { LivePlots } from "/assets/components/tools/plots.js"
|
||||
import { ThemeMaker } from "/assets/components/tools/theme_maker.js"
|
||||
import { TestingGround } from "/assets/components/tools/testing_ground.js"
|
||||
import { Tuning } from "/assets/components/tools/tuning.js?v=ftm-workspace-9"
|
||||
import { Tuning } from "/assets/components/tools/tuning.js?v=flm-workspace-9"
|
||||
import { Troubleshoot } from "/assets/components/tools/troubleshoot.js"
|
||||
import { TmuxLog } from "/assets/components/tools/tmux.js"
|
||||
import { ToggleControl } from "/assets/components/tools/toggles.js"
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.ds-ftm-badge {
|
||||
.ds-flm-badge {
|
||||
background: var(--sidebar-bg);
|
||||
border: 1px solid var(--main-fg);
|
||||
border-radius: 999px;
|
||||
@@ -191,8 +191,8 @@
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ds-ftm-detail,
|
||||
.ds-ftm-summary {
|
||||
.ds-flm-detail,
|
||||
.ds-flm-summary {
|
||||
border-left: 2px solid var(--main-fg);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
@@ -201,11 +201,11 @@
|
||||
padding-left: 0.65rem;
|
||||
}
|
||||
|
||||
.ds-ftm-summary > div + div {
|
||||
.ds-flm-summary > div + div {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.ds-ftm-link {
|
||||
.ds-flm-link {
|
||||
color: var(--main-fg);
|
||||
display: inline-block;
|
||||
font-weight: var(--font-weight-bold);
|
||||
@@ -213,7 +213,7 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ds-ftm-link:hover {
|
||||
.ds-flm-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ const FAVORITE_OPTION_COLLATOR = new Intl.Collator(undefined, { numeric: true, s
|
||||
// Plain variables — scheduling/routing flags that must NOT be reactive
|
||||
let syncScheduled = false
|
||||
let lastParams = null
|
||||
let ftmWorkspaceInflight = null
|
||||
let lastFtmWorkspaceFetch = 0
|
||||
let flmWorkspaceInflight = null
|
||||
let lastFlmWorkspaceFetch = 0
|
||||
const DYNAMIC_DEFAULT_DEP_KEYS = new Set(["AccelerationProfile", "EVTuning", "TruckTuning"])
|
||||
const PANDA_FIRMWARE_TOGGLE_KEYS = new Set(["IgnoreIgnitionLine", "RemoteStartBootsComma", "HKGRemoteStartBootsComma"])
|
||||
const FTM_ADVANCED_LATERAL_KEYS = new Set([
|
||||
const FLM_ADVANCED_LATERAL_KEYS = new Set([
|
||||
"AdvancedLateralTune", "ForceAutoTune", "ForceAutoTuneOff", "UseAutoSteerDelay", "SteerDelay",
|
||||
"SteerFriction", "SteerKP", "SteerLatAccel", "SteerRatio",
|
||||
])
|
||||
@@ -28,7 +28,7 @@ const state = reactive({
|
||||
paramMetaByKey: {},
|
||||
values: {},
|
||||
defaultValues: {},
|
||||
ftmActiveTrial: null,
|
||||
flmActiveTrial: null,
|
||||
loadingLayout: true,
|
||||
loadingValues: true,
|
||||
filter: "",
|
||||
@@ -274,28 +274,28 @@ async function fetchDefaultValues() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchFtmWorkspace(force = false) {
|
||||
async function fetchFlmWorkspace(force = false) {
|
||||
const now = Date.now()
|
||||
if (!force && now - lastFtmWorkspaceFetch < 1500) return
|
||||
if (ftmWorkspaceInflight) return ftmWorkspaceInflight
|
||||
if (!force && now - lastFlmWorkspaceFetch < 1500) return
|
||||
if (flmWorkspaceInflight) return flmWorkspaceInflight
|
||||
|
||||
lastFtmWorkspaceFetch = now
|
||||
ftmWorkspaceInflight = fetch("/api/ftm/workspace", { cache: "no-store" })
|
||||
lastFlmWorkspaceFetch = now
|
||||
flmWorkspaceInflight = fetch("/api/flm/workspace", { cache: "no-store" })
|
||||
.then(async res => {
|
||||
if (!res.ok) return
|
||||
const workspace = await res.json()
|
||||
state.ftmActiveTrial = workspace?.activeTrial || null
|
||||
state.flmActiveTrial = workspace?.activeTrial || null
|
||||
})
|
||||
.catch(error => console.warn("Failed to load active FTM trial state:", error))
|
||||
.catch(error => console.warn("Failed to load active FLM trial state:", error))
|
||||
.finally(() => {
|
||||
ftmWorkspaceInflight = null
|
||||
flmWorkspaceInflight = null
|
||||
})
|
||||
|
||||
return ftmWorkspaceInflight
|
||||
return flmWorkspaceInflight
|
||||
}
|
||||
|
||||
async function refreshParamsAndDefaults() {
|
||||
await Promise.all([fetchDefaultValues(), fetchFtmWorkspace(true)])
|
||||
await Promise.all([fetchDefaultValues(), fetchFlmWorkspace(true)])
|
||||
|
||||
try {
|
||||
const valuesRes = await fetch("/api/params/all")
|
||||
@@ -344,7 +344,7 @@ async function fetchLayoutAndParams() {
|
||||
|
||||
// Pull params once at page load; local state handles subsequent edits.
|
||||
try {
|
||||
const [defaultsLoaded] = await Promise.all([fetchDefaultValues(), fetchFtmWorkspace(true)])
|
||||
const [defaultsLoaded] = await Promise.all([fetchDefaultValues(), fetchFlmWorkspace(true)])
|
||||
if (!defaultsLoaded) {
|
||||
state.defaultValues = {}
|
||||
}
|
||||
@@ -1035,9 +1035,9 @@ function valuesEqual(left, right) {
|
||||
return left === right
|
||||
}
|
||||
|
||||
function getFtmParamStatus(key) {
|
||||
const trial = state.ftmActiveTrial
|
||||
if (!trial || !FTM_ADVANCED_LATERAL_KEYS.has(key)) return null
|
||||
function getFlmParamStatus(key) {
|
||||
const trial = state.flmActiveTrial
|
||||
if (!trial || !FLM_ADVANCED_LATERAL_KEYS.has(key)) return null
|
||||
|
||||
const applied = trial.appliedGenericParams || {}
|
||||
const hasExplicitMetadata = Object.prototype.hasOwnProperty.call(applied, key)
|
||||
@@ -1054,7 +1054,7 @@ function getFtmParamStatus(key) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatFtmValue(param, value) {
|
||||
function formatFlmValue(param, value) {
|
||||
if (value === undefined || value === null) return "not set"
|
||||
if (param.data_type === "bool") return value ? "On" : "Off"
|
||||
if (param.ui_type === "numeric") {
|
||||
@@ -1064,8 +1064,8 @@ function formatFtmValue(param, value) {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
function getFtmTrialSummary() {
|
||||
const trial = state.ftmActiveTrial
|
||||
function getFlmTrialSummary() {
|
||||
const trial = state.flmActiveTrial
|
||||
if (!trial) return null
|
||||
const genericCount = Object.keys(trial.appliedGenericParams || {}).filter(key => key !== "AdvancedLateralTune").length
|
||||
const thresholdCount = Object.keys(trial.appliedFrictionThresholds || {}).length
|
||||
@@ -1234,8 +1234,8 @@ function renderSettingRow(p) {
|
||||
const isChild = p.parent_key ? "ds-child-modifier" : ""
|
||||
const lockReason = () => getSettingLockReason(p)
|
||||
const isLocked = () => lockReason() !== ""
|
||||
const ftmParamStatus = getFtmParamStatus(p.key)
|
||||
const ftmTrialSummary = p.key === "AdvancedLateralTune" ? getFtmTrialSummary() : null
|
||||
const flmParamStatus = getFlmParamStatus(p.key)
|
||||
const flmTrialSummary = p.key === "AdvancedLateralTune" ? getFlmTrialSummary() : null
|
||||
let rowControl = ""
|
||||
|
||||
if (isNumeric) {
|
||||
@@ -1358,30 +1358,30 @@ function renderSettingRow(p) {
|
||||
<div class="ds-row-text">
|
||||
<div class="ds-row-heading">
|
||||
<span class="ds-row-label">${p.label}</span>
|
||||
${ftmParamStatus ? html`<span class="ds-ftm-badge">Currently overridden by FTM</span>` : ""}
|
||||
${flmParamStatus ? html`<span class="ds-flm-badge">Currently overridden by FLM</span>` : ""}
|
||||
</div>
|
||||
${p.description ? html`<div class="ds-row-desc">${p.description}</div>` : ""}
|
||||
${() => {
|
||||
const reason = lockReason()
|
||||
return reason ? html`<div class="ds-row-desc"><strong>Locked:</strong> ${reason}</div>` : ""
|
||||
}}
|
||||
${ftmParamStatus ? html`
|
||||
<div class="ds-ftm-detail">
|
||||
Effective now: <strong>${formatFtmValue(p, ftmParamStatus.effectiveValue)}</strong>.
|
||||
Revert restores: <strong>${formatFtmValue(p, ftmParamStatus.previousValue)}</strong>.
|
||||
${flmParamStatus ? html`
|
||||
<div class="ds-flm-detail">
|
||||
Effective now: <strong>${formatFlmValue(p, flmParamStatus.effectiveValue)}</strong>.
|
||||
Revert restores: <strong>${formatFlmValue(p, flmParamStatus.previousValue)}</strong>.
|
||||
You can still edit this while the trial is active.
|
||||
</div>
|
||||
` : ""}
|
||||
${ftmTrialSummary ? html`
|
||||
<div class="ds-ftm-summary">
|
||||
<div><strong>FTM trial active:</strong> ${ftmTrialSummary.title}</div>
|
||||
${flmTrialSummary ? html`
|
||||
<div class="ds-flm-summary">
|
||||
<div><strong>FLM trial active:</strong> ${flmTrialSummary.title}</div>
|
||||
<div>
|
||||
${ftmTrialSummary.genericCount} advanced setting${ftmTrialSummary.genericCount === 1 ? "" : "s"},
|
||||
${ftmTrialSummary.thresholdCount} friction curve${ftmTrialSummary.thresholdCount === 1 ? "" : "s"}, and
|
||||
${ftmTrialSummary.vehicleKnobCount} vehicle-specific knob${ftmTrialSummary.vehicleKnobCount === 1 ? "" : "s"} active.
|
||||
${flmTrialSummary.genericCount} advanced setting${flmTrialSummary.genericCount === 1 ? "" : "s"},
|
||||
${flmTrialSummary.thresholdCount} friction curve${flmTrialSummary.thresholdCount === 1 ? "" : "s"}, and
|
||||
${flmTrialSummary.vehicleKnobCount} vehicle-specific knob${flmTrialSummary.vehicleKnobCount === 1 ? "" : "s"} active.
|
||||
</div>
|
||||
<div>Revert from Lateral Tuning restores the exact settings saved before this trial.</div>
|
||||
<a class="ds-ftm-link" href="/tuning">Open Lateral Tuning</a>
|
||||
<a class="ds-flm-link" href="/tuning">Open Lateral Tuning</a>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
@@ -1446,7 +1446,7 @@ function resolveActiveSectionSlug(params) {
|
||||
export function DeviceSettings({ params }) {
|
||||
lastParams = params
|
||||
|
||||
fetchFtmWorkspace()
|
||||
fetchFlmWorkspace()
|
||||
|
||||
if (!state.fetched) {
|
||||
state.fetched = true
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.ftmTwoColumn {
|
||||
.flmTwoColumn {
|
||||
display: grid;
|
||||
gap: var(--gap-base);
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin-top: var(--margin-base);
|
||||
}
|
||||
|
||||
.ftmCard {
|
||||
.flmCard {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--border-radius-md);
|
||||
@@ -13,45 +13,51 @@
|
||||
padding: var(--padding-base);
|
||||
}
|
||||
|
||||
.ftmCardHeader {
|
||||
.flmCardHeader {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ftmCardHeader h3,
|
||||
.ftmCardHeader h4,
|
||||
.ftmCardHeader h5 {
|
||||
.flmCardHeader h3,
|
||||
.flmCardHeader h4,
|
||||
.flmCardHeader h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ftmCardSubsection {
|
||||
.flmCardSubsection {
|
||||
margin-top: var(--margin-base);
|
||||
}
|
||||
|
||||
.ftmRouteList,
|
||||
.ftmWorkspaceList,
|
||||
.ftmFindings {
|
||||
.flmRouteList,
|
||||
.flmWorkspaceList,
|
||||
.flmFindings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
margin-top: var(--margin-base);
|
||||
}
|
||||
|
||||
.ftmWorkspaceRow {
|
||||
.flmWorkspaceRow {
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.ftmRouteList {
|
||||
.flmRouteList {
|
||||
max-height: 28rem;
|
||||
overflow: auto;
|
||||
padding-right: var(--padding-xs);
|
||||
}
|
||||
|
||||
.ftmRouteItem,
|
||||
.ftmWorkspaceItem {
|
||||
.flmRouteRow {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.flmRouteItem,
|
||||
.flmWorkspaceItem {
|
||||
align-items: flex-start;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
@@ -64,61 +70,83 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ftmRouteItem input {
|
||||
.flmRouteItem input {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.ftmRouteItem span,
|
||||
.ftmWorkspaceItem {
|
||||
.flmRouteItem {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.flmConnectLink {
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--main-fg);
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.78rem;
|
||||
padding: 0 var(--padding-sm);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.flmConnectLink:hover {
|
||||
border-color: var(--main-fg);
|
||||
}
|
||||
|
||||
.flmRouteItem span,
|
||||
.flmWorkspaceItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ftmWorkspaceItem {
|
||||
.flmWorkspaceItem {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.ftmWorkspaceDelete {
|
||||
.flmWorkspaceDelete {
|
||||
align-self: stretch;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.ftmRouteItem small,
|
||||
.ftmWorkspaceItem small,
|
||||
.ftmWorkspaceItem span {
|
||||
.flmRouteItem small,
|
||||
.flmWorkspaceItem small,
|
||||
.flmWorkspaceItem span {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.ftmWorkspaceItem:hover,
|
||||
.ftmRouteItem:hover,
|
||||
.ftmCard button.selected {
|
||||
.flmWorkspaceItem:hover,
|
||||
.flmRouteItem:hover,
|
||||
.flmCard button.selected {
|
||||
border-color: var(--main-fg);
|
||||
}
|
||||
|
||||
.ftmProfileGrid {
|
||||
.flmProfileGrid {
|
||||
display: grid;
|
||||
gap: var(--gap-base);
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.ftmFeedbackButtons {
|
||||
.flmFeedbackButtons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-xs);
|
||||
}
|
||||
|
||||
.ftmFeedbackButtons .longManeuverButton.selected {
|
||||
.flmFeedbackButtons .longManeuverButton.selected {
|
||||
box-shadow: 0 0 0 3px var(--text-color);
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.ftmTuneComparison {
|
||||
.flmTuneComparison {
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: var(--padding-base);
|
||||
}
|
||||
|
||||
.ftmTuneComparisonTable {
|
||||
.flmTuneComparisonTable {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
gap: var(--gap-xs) var(--gap-sm);
|
||||
@@ -126,40 +154,40 @@
|
||||
margin-top: var(--margin-sm);
|
||||
}
|
||||
|
||||
.ftmTuneComparisonTable > div {
|
||||
.flmTuneComparisonTable > div {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
padding: var(--padding-xs) 0;
|
||||
}
|
||||
|
||||
.ftmTuneComparisonHeader {
|
||||
.flmTuneComparisonHeader {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-demi-bold);
|
||||
}
|
||||
|
||||
.ftmTuneComparisonLabel {
|
||||
.flmTuneComparisonLabel {
|
||||
font-weight: var(--font-weight-demi-bold);
|
||||
}
|
||||
|
||||
.ftmTuneComparisonArrow {
|
||||
.flmTuneComparisonArrow {
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ftmTuneComparisonChanged {
|
||||
.flmTuneComparisonChanged {
|
||||
color: var(--main-fg);
|
||||
font-weight: var(--font-weight-demi-bold);
|
||||
}
|
||||
|
||||
.ftmDeltaBox {
|
||||
.flmDeltaBox {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: var(--border-radius-sm);
|
||||
margin-top: var(--margin-sm);
|
||||
padding: var(--padding-sm);
|
||||
}
|
||||
|
||||
.ftmNotes {
|
||||
.flmNotes {
|
||||
background: var(--secondary-bg);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--border-radius-sm);
|
||||
@@ -171,19 +199,19 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ftmTrackingOverview {
|
||||
.flmTrackingOverview {
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: var(--padding-base);
|
||||
}
|
||||
|
||||
.ftmTrackingLegend {
|
||||
.flmTrackingLegend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.ftmTrackingLegend span {
|
||||
.flmTrackingLegend span {
|
||||
align-items: center;
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
@@ -191,22 +219,22 @@
|
||||
gap: var(--gap-xs);
|
||||
}
|
||||
|
||||
.ftmTrackingLegend i {
|
||||
.flmTrackingLegend i {
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
height: 0.2rem;
|
||||
width: 1.4rem;
|
||||
}
|
||||
|
||||
.ftmTrackingLegend i.desired {
|
||||
.flmTrackingLegend i.desired {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.ftmTrackingLegend i.actual {
|
||||
.flmTrackingLegend i.actual {
|
||||
background: #38bdf8;
|
||||
}
|
||||
|
||||
.ftmTrackingNotice {
|
||||
.flmTrackingNotice {
|
||||
border-left: 2px solid var(--main-fg);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
@@ -214,14 +242,14 @@
|
||||
padding: var(--padding-xs) var(--padding-sm);
|
||||
}
|
||||
|
||||
.ftmTrackingGrid {
|
||||
.flmTrackingGrid {
|
||||
display: grid;
|
||||
gap: var(--gap-sm);
|
||||
grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
|
||||
margin-top: var(--margin-base);
|
||||
}
|
||||
|
||||
.ftmTrackingCard {
|
||||
.flmTrackingCard {
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: var(--border-radius-sm);
|
||||
@@ -229,25 +257,25 @@
|
||||
padding: var(--padding-sm);
|
||||
}
|
||||
|
||||
.ftmTrackingCardHeader {
|
||||
.flmTrackingCardHeader {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ftmTrackingCardHeader > div {
|
||||
.flmTrackingCardHeader > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ftmTrackingCardHeader span,
|
||||
.ftmTrackingCard small {
|
||||
.flmTrackingCardHeader span,
|
||||
.flmTrackingCard small {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.ftmTrackingCard small {
|
||||
.flmTrackingCard small {
|
||||
display: block;
|
||||
margin-top: var(--margin-xs);
|
||||
overflow: hidden;
|
||||
@@ -255,35 +283,35 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ftmTrackingPlot {
|
||||
.flmTrackingPlot {
|
||||
display: block;
|
||||
height: auto;
|
||||
margin-top: var(--margin-xs);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ftmTrackingPlotBackground {
|
||||
.flmTrackingPlotBackground {
|
||||
fill: var(--input-bg);
|
||||
}
|
||||
|
||||
.ftmTrackingEventRegion {
|
||||
.flmTrackingEventRegion {
|
||||
fill: rgba(139, 108, 197, 0.14);
|
||||
}
|
||||
|
||||
.ftmTrackingAxis,
|
||||
.ftmTrackingZero {
|
||||
.flmTrackingAxis,
|
||||
.flmTrackingZero {
|
||||
fill: none;
|
||||
stroke: rgba(255, 255, 255, 0.18);
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.ftmTrackingZero {
|
||||
.flmTrackingZero {
|
||||
stroke-dasharray: 3 4;
|
||||
}
|
||||
|
||||
.ftmTrackingDesired,
|
||||
.ftmTrackingActual {
|
||||
.flmTrackingDesired,
|
||||
.flmTrackingActual {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
@@ -291,27 +319,27 @@
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.ftmTrackingDesired {
|
||||
.flmTrackingDesired {
|
||||
stroke: #ef4444;
|
||||
}
|
||||
|
||||
.ftmTrackingActual {
|
||||
.flmTrackingActual {
|
||||
stroke: #38bdf8;
|
||||
}
|
||||
|
||||
.ftmTrackingAxisLabel {
|
||||
.flmTrackingAxisLabel {
|
||||
fill: var(--text-muted);
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.ftmTrackingMeta {
|
||||
.flmTrackingMeta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-xs);
|
||||
margin-top: var(--margin-xs);
|
||||
}
|
||||
|
||||
.ftmTrackingMeta span {
|
||||
.flmTrackingMeta span {
|
||||
background: var(--input-bg);
|
||||
border-radius: 999px;
|
||||
color: var(--text-muted);
|
||||
@@ -320,12 +348,12 @@
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
.ftmTwoColumn,
|
||||
.ftmProfileGrid {
|
||||
.flmTwoColumn,
|
||||
.flmProfileGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.ftmTuneComparisonTable {
|
||||
.flmTuneComparisonTable {
|
||||
font-size: var(--font-size-sm);
|
||||
grid-template-columns: minmax(8rem, 1.3fr) minmax(5rem, 1fr) auto minmax(5rem, 1fr);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const state = reactive({
|
||||
truncatedRoutes: false,
|
||||
routeProgress: 0,
|
||||
routeTotal: 0,
|
||||
connectDongleId: "",
|
||||
workspace: { reports: [], activeTrial: null, status: {} },
|
||||
status: {},
|
||||
report: null,
|
||||
@@ -46,6 +47,13 @@ function safeCount(value) {
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
function connectRouteUrl(routeName) {
|
||||
const dongleId = String(state.connectDongleId || "").trim()
|
||||
const routeId = String(routeName || "").trim()
|
||||
if (!dongleId || !routeId) return ""
|
||||
return `https://connect.comma.ai/${encodeURIComponent(dongleId)}/${encodeURIComponent(routeId)}`
|
||||
}
|
||||
|
||||
function formatStatusAge(updatedAt) {
|
||||
const updated = Number(updatedAt)
|
||||
if (!Number.isFinite(updated) || updated <= 0) return "unknown"
|
||||
@@ -151,11 +159,14 @@ async function fetchRoutes() {
|
||||
state.routeProgress = safeCount(data.progress)
|
||||
state.routeTotal = safeCount(data.total)
|
||||
}
|
||||
if (typeof data.connectDongleId === "string") {
|
||||
state.connectDongleId = data.connectDongleId
|
||||
}
|
||||
if (Array.isArray(data.routes)) {
|
||||
enqueueRoutes(data.routes)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[ftm] failed to parse route payload", error)
|
||||
console.error("[flm] failed to parse route payload", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +190,7 @@ async function fetchRoutes() {
|
||||
async function fetchWorkspace() {
|
||||
try {
|
||||
state.loadingWorkspace = true
|
||||
const response = await fetch("/api/ftm/workspace")
|
||||
const response = await fetch("/api/flm/workspace")
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to load tuning workspace.")
|
||||
state.workspace = payload
|
||||
@@ -201,7 +212,7 @@ function syncFeedbackState(report) {
|
||||
async function loadReport(reportId) {
|
||||
if (!reportId) return
|
||||
try {
|
||||
const response = await fetch(`/api/ftm/report/${encodeURIComponent(reportId)}`)
|
||||
const response = await fetch(`/api/flm/report/${encodeURIComponent(reportId)}`)
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to load tuning report.")
|
||||
state.report = payload
|
||||
@@ -218,7 +229,7 @@ async function deleteReport(reportId) {
|
||||
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch(`/api/ftm/report/${encodeURIComponent(reportId)}`, { method: "DELETE" })
|
||||
const response = await fetch(`/api/flm/report/${encodeURIComponent(reportId)}`, { method: "DELETE" })
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to delete tuning report.")
|
||||
|
||||
@@ -243,7 +254,7 @@ async function clearWorkspace() {
|
||||
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/workspace/clear", { method: "POST" })
|
||||
const response = await fetch("/api/flm/workspace/clear", { method: "POST" })
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to clear tuning workspace.")
|
||||
|
||||
@@ -262,7 +273,7 @@ async function clearWorkspace() {
|
||||
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
const response = await fetch("/api/ftm/status")
|
||||
const response = await fetch("/api/flm/status")
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to load tuning status.")
|
||||
state.status = {
|
||||
@@ -323,7 +334,7 @@ async function runAnalyze() {
|
||||
if (!state.selectedRoutes.length || state.runningAction) return
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/analyze", {
|
||||
const response = await fetch("/api/flm/analyze", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ routes: state.selectedRoutes }),
|
||||
@@ -331,7 +342,7 @@ async function runAnalyze() {
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to start tuning analysis.")
|
||||
state.status = payload.status || {}
|
||||
showSnackbar(payload.message || "FTM analysis started.")
|
||||
showSnackbar(payload.message || "FLM analysis started.")
|
||||
} catch (error) {
|
||||
state.error = error?.message || "Failed to start tuning analysis."
|
||||
showSnackbar(state.error, "error")
|
||||
@@ -344,11 +355,11 @@ async function stopAnalyze() {
|
||||
if (state.runningAction) return
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/analyze/stop", { method: "POST" })
|
||||
const response = await fetch("/api/flm/analyze/stop", { method: "POST" })
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to stop tuning analysis.")
|
||||
state.status = payload.status || {}
|
||||
showSnackbar(payload.message || "FTM analysis stopped.")
|
||||
showSnackbar(payload.message || "FLM analysis stopped.")
|
||||
} catch (error) {
|
||||
state.error = error?.message || "Failed to stop tuning analysis."
|
||||
showSnackbar(state.error, "error")
|
||||
@@ -361,7 +372,7 @@ async function applyProfile(profileId) {
|
||||
if (!state.report?.reportId || !profileId || state.runningAction) return
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/trials/apply", {
|
||||
const response = await fetch("/api/flm/trials/apply", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ reportId: state.report.reportId, profileId }),
|
||||
@@ -385,7 +396,7 @@ async function selectPath(pathKey) {
|
||||
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch(`/api/ftm/report/${encodeURIComponent(state.report.reportId)}/path`, {
|
||||
const response = await fetch(`/api/flm/report/${encodeURIComponent(state.report.reportId)}/path`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ pathKey }),
|
||||
@@ -407,7 +418,7 @@ async function revertProfile() {
|
||||
if (state.runningAction) return
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/trials/revert", { method: "POST" })
|
||||
const response = await fetch("/api/flm/trials/revert", { method: "POST" })
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to revert trial profile.")
|
||||
state.error = ""
|
||||
@@ -427,7 +438,7 @@ async function acceptCurrentAsBaseline() {
|
||||
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/trials/accept", { method: "POST" })
|
||||
const response = await fetch("/api/flm/trials/accept", { method: "POST" })
|
||||
const payload = await response.json()
|
||||
if (!response.ok) throw new Error(payload.error || "Failed to keep the current tune.")
|
||||
state.error = ""
|
||||
@@ -463,7 +474,7 @@ async function saveFeedback() {
|
||||
if (!state.report?.reportId || state.runningAction) return
|
||||
state.runningAction = true
|
||||
try {
|
||||
const response = await fetch("/api/ftm/feedback", {
|
||||
const response = await fetch("/api/flm/feedback", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
@@ -548,9 +559,9 @@ function activeTrialProfile() {
|
||||
return allReportProfiles().find((profile) => profile.id === activeTrial.profileId) || null
|
||||
}
|
||||
|
||||
function mergedFtmOverrides() {
|
||||
const current = state.report?.currentParams?.FTMActiveOverrides || {}
|
||||
const trial = activeTrialProfile()?.ftmOverrides || {}
|
||||
function mergedFlmOverrides() {
|
||||
const current = state.report?.currentParams?.FLMActiveOverrides || {}
|
||||
const trial = activeTrialProfile()?.flmOverrides || {}
|
||||
return {
|
||||
baseFrictionThresholds: {
|
||||
...(current.baseFrictionThresholds || {}),
|
||||
@@ -590,8 +601,8 @@ function tuneComparisonRows() {
|
||||
current: Object.hasOwn(trialGeneric, key) ? trialGeneric[key] : current[key],
|
||||
}))
|
||||
|
||||
const overrides = mergedFtmOverrides()
|
||||
for (const [family, payload] of Object.entries(stock.FTMBaseFrictionThresholds || {})) {
|
||||
const overrides = mergedFlmOverrides()
|
||||
for (const [family, payload] of Object.entries(stock.FLMBaseFrictionThresholds || {})) {
|
||||
rows.push({
|
||||
key: `friction-threshold-${family}`,
|
||||
label: `${family} friction threshold`,
|
||||
@@ -601,11 +612,11 @@ function tuneComparisonRows() {
|
||||
}
|
||||
|
||||
for (const [symbol, currentValue] of Object.entries(overrides.vehicleKnobs || {})) {
|
||||
if (!Object.hasOwn(stock.FTMVehicleKnobs || {}, symbol)) continue
|
||||
if (!Object.hasOwn(stock.FLMVehicleKnobs || {}, symbol)) continue
|
||||
rows.push({
|
||||
key: symbol,
|
||||
label: symbol.split(".").slice(1).join("."),
|
||||
stock: stock.FTMVehicleKnobs[symbol],
|
||||
stock: stock.FLMVehicleKnobs[symbol],
|
||||
current: currentValue,
|
||||
codeLabel: symbol,
|
||||
})
|
||||
@@ -625,25 +636,25 @@ function renderTuneComparison() {
|
||||
if (!rows.length) return ""
|
||||
const profile = activeTrialProfile()
|
||||
return html`
|
||||
<div class="ftmCardSubsection ftmTuneComparison">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmCardSubsection flmTuneComparison">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h4>Stock vs Current FTM</h4>
|
||||
<h4>Stock vs Current FLM</h4>
|
||||
<p class="longManeuverMuted">
|
||||
${profile ? `Includes active trial: ${profile.pathLabel || "FTM"} / ${profile.label}` : "Current values captured when this route was analyzed."}
|
||||
${profile ? `Includes active trial: ${profile.pathLabel || "FLM"} / ${profile.label}` : "Current values captured when this route was analyzed."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ftmTuneComparisonTable">
|
||||
<div class="ftmTuneComparisonHeader">Parameter</div>
|
||||
<div class="ftmTuneComparisonHeader">Stock</div>
|
||||
<div class="ftmTuneComparisonArrow"></div>
|
||||
<div class="ftmTuneComparisonHeader">FTM</div>
|
||||
<div class="flmTuneComparisonTable">
|
||||
<div class="flmTuneComparisonHeader">Parameter</div>
|
||||
<div class="flmTuneComparisonHeader">Stock</div>
|
||||
<div class="flmTuneComparisonArrow"></div>
|
||||
<div class="flmTuneComparisonHeader">FLM</div>
|
||||
${rows.map((row) => html`
|
||||
<div class="ftmTuneComparisonLabel" title="${row.codeLabel || row.key}">${row.label}</div>
|
||||
<div class="flmTuneComparisonLabel" title="${row.codeLabel || row.key}">${row.label}</div>
|
||||
<div>${formatTuneComparisonValue(row.stock)}</div>
|
||||
<div class="ftmTuneComparisonArrow">></div>
|
||||
<div class="${comparisonValueChanged(row) ? "ftmTuneComparisonChanged" : ""}">${formatTuneComparisonValue(row.current)}</div>
|
||||
<div class="flmTuneComparisonArrow">></div>
|
||||
<div class="${comparisonValueChanged(row) ? "flmTuneComparisonChanged" : ""}">${formatTuneComparisonValue(row.current)}</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -731,18 +742,18 @@ function renderTrackingPlot(plot) {
|
||||
const zeroY = 136 - ((0 - yMin) / Math.max(yMax - yMin, 0.001)) * 124
|
||||
|
||||
return html`
|
||||
<svg class="ftmTrackingPlot" viewBox="0 0 420 158" role="img" aria-label="Desired versus actual lateral acceleration">
|
||||
<rect class="ftmTrackingPlotBackground" x="0" y="0" width="420" height="158" rx="10"></rect>
|
||||
<rect class="ftmTrackingEventRegion" x="${eventX}" y="12" width="${eventWidth}" height="124"></rect>
|
||||
<line class="ftmTrackingZero" x1="34" y1="${zeroY}" x2="410" y2="${zeroY}"></line>
|
||||
<line class="ftmTrackingAxis" x1="34" y1="12" x2="34" y2="136"></line>
|
||||
<line class="ftmTrackingAxis" x1="34" y1="136" x2="410" y2="136"></line>
|
||||
<polyline class="ftmTrackingDesired" points="${desiredPoints}"></polyline>
|
||||
<polyline class="ftmTrackingActual" points="${actualPoints}"></polyline>
|
||||
<text class="ftmTrackingAxisLabel" x="2" y="18">${yMax.toFixed(1)}</text>
|
||||
<text class="ftmTrackingAxisLabel" x="2" y="138">${yMin.toFixed(1)}</text>
|
||||
<text class="ftmTrackingAxisLabel" x="34" y="152">0s</text>
|
||||
<text class="ftmTrackingAxisLabel" x="384" y="152">${duration.toFixed(1)}s</text>
|
||||
<svg class="flmTrackingPlot" viewBox="0 0 420 158" role="img" aria-label="Desired versus actual lateral acceleration">
|
||||
<rect class="flmTrackingPlotBackground" x="0" y="0" width="420" height="158" rx="10"></rect>
|
||||
<rect class="flmTrackingEventRegion" x="${eventX}" y="12" width="${eventWidth}" height="124"></rect>
|
||||
<line class="flmTrackingZero" x1="34" y1="${zeroY}" x2="410" y2="${zeroY}"></line>
|
||||
<line class="flmTrackingAxis" x1="34" y1="12" x2="34" y2="136"></line>
|
||||
<line class="flmTrackingAxis" x1="34" y1="136" x2="410" y2="136"></line>
|
||||
<polyline class="flmTrackingDesired" points="${desiredPoints}"></polyline>
|
||||
<polyline class="flmTrackingActual" points="${actualPoints}"></polyline>
|
||||
<text class="flmTrackingAxisLabel" x="2" y="18">${yMax.toFixed(1)}</text>
|
||||
<text class="flmTrackingAxisLabel" x="2" y="138">${yMin.toFixed(1)}</text>
|
||||
<text class="flmTrackingAxisLabel" x="34" y="152">0s</text>
|
||||
<text class="flmTrackingAxisLabel" x="384" y="152">${duration.toFixed(1)}s</text>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
@@ -752,28 +763,28 @@ function renderTrackingOverview() {
|
||||
if (!items.length) return ""
|
||||
|
||||
return html`
|
||||
<div class="ftmCardSubsection ftmTrackingOverview">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmCardSubsection flmTrackingOverview">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h4>Tracking Overview</h4>
|
||||
<p class="longManeuverMuted">
|
||||
Desired vs actual lateral acceleration (m/s^2) in representative intervention-free windows. The shaded area is the classified event.
|
||||
</p>
|
||||
</div>
|
||||
<div class="ftmTrackingLegend" aria-label="Plot legend">
|
||||
<div class="flmTrackingLegend" aria-label="Plot legend">
|
||||
<span><i class="desired"></i>Desired</span>
|
||||
<span><i class="actual"></i>Actual</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ftmTrackingNotice">
|
||||
<div class="flmTrackingNotice">
|
||||
No fit score by design. Small phase separation is normal, and closer traces do not automatically mean the steering feels better.
|
||||
</div>
|
||||
|
||||
<div class="ftmTrackingGrid">
|
||||
<div class="flmTrackingGrid">
|
||||
${items.map((item) => html`
|
||||
<article class="ftmTrackingCard">
|
||||
<div class="ftmTrackingCardHeader">
|
||||
<article class="flmTrackingCard">
|
||||
<div class="flmTrackingCardHeader">
|
||||
<div>
|
||||
<strong>${item.overviewTitle}</strong>
|
||||
<span>${String(item.bucket || "event").replace(/_/g, " ")}</span>
|
||||
@@ -781,7 +792,7 @@ function renderTrackingOverview() {
|
||||
<span>${Number(item.plotData.meanSpeedMph || 0).toFixed(1)} mph</span>
|
||||
</div>
|
||||
${renderTrackingPlot(item.plotData)}
|
||||
<div class="ftmTrackingMeta">
|
||||
<div class="flmTrackingMeta">
|
||||
<span>${item.evidence?.directionBias || item.plotData.direction || "center"}</span>
|
||||
<span>${item.evidence?.speedBand || item.plotData.speedBand || "mixed"}</span>
|
||||
<span>${Number(item.plotData.eventDurationSec || 0).toFixed(1)}s event</span>
|
||||
@@ -796,11 +807,11 @@ function renderTrackingOverview() {
|
||||
|
||||
function renderProfile(profile) {
|
||||
const genericEntries = Object.entries(profile.genericParams || {}).filter(([key]) => key !== "AdvancedLateralTune")
|
||||
const frictionEntries = Object.entries(profile.ftmOverrides?.baseFrictionThresholds || {})
|
||||
const vehicleKnobEntries = Object.entries(profile.ftmOverrides?.vehicleKnobs || {})
|
||||
const frictionEntries = Object.entries(profile.flmOverrides?.baseFrictionThresholds || {})
|
||||
const vehicleKnobEntries = Object.entries(profile.flmOverrides?.vehicleKnobs || {})
|
||||
return html`
|
||||
<div class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h4>${profile.label}</h4>
|
||||
<p class="longManeuverMuted">${profile.description}</p>
|
||||
@@ -813,7 +824,7 @@ function renderProfile(profile) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ftmProfileGrid">
|
||||
<div class="flmProfileGrid">
|
||||
<div>
|
||||
<h5>Generic Params</h5>
|
||||
<ul>
|
||||
@@ -823,7 +834,7 @@ function renderProfile(profile) {
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h5>FTM Overrides</h5>
|
||||
<h5>FLM Overrides</h5>
|
||||
<ul>
|
||||
${frictionEntries.map(([family, payload]) => html`<li><code>${family}</code>: ${renderCurve(payload?.values || [])}</li>`)}
|
||||
${vehicleKnobEntries.map(([key, value]) => html`<li><code>${key}</code>: ${Number(value).toFixed(3)}</li>`)}
|
||||
@@ -838,8 +849,8 @@ function renderProfile(profile) {
|
||||
function renderSuggestion(suggestion) {
|
||||
const currentVsSuggested = suggestion.currentVsSuggested
|
||||
return html`
|
||||
<div class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h4>${suggestion.bucket.replace(/_/g, " ")}</h4>
|
||||
<p class="longManeuverMuted">
|
||||
@@ -848,7 +859,7 @@ function renderSuggestion(suggestion) {
|
||||
${safeCount(suggestion.evidence?.eventCount)} event(s)
|
||||
</p>
|
||||
</div>
|
||||
<div class="ftmFeedbackButtons">
|
||||
<div class="flmFeedbackButtons">
|
||||
<button
|
||||
class="${() => `longManeuverButton ${feedbackStateFor(suggestion.dimensionId) === "accepted" ? "selected" : ""}`}"
|
||||
aria-pressed="${() => feedbackStateFor(suggestion.dimensionId) === "accepted" ? "true" : "false"}"
|
||||
@@ -875,7 +886,7 @@ function renderSuggestion(suggestion) {
|
||||
|
||||
${currentVsSuggested
|
||||
? html`
|
||||
<div class="ftmDeltaBox">
|
||||
<div class="flmDeltaBox">
|
||||
<strong>Current vs suggested:</strong>
|
||||
${currentVsSuggested.type === "friction_curve"
|
||||
? html`
|
||||
@@ -899,8 +910,8 @@ function renderSuggestion(suggestion) {
|
||||
function renderPathSummary(path) {
|
||||
const selected = path.key === (state.report?.selectedPathKey || state.report?.primaryPathKey)
|
||||
return html`
|
||||
<div class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h4>${path.title}</h4>
|
||||
<p class="longManeuverMuted">
|
||||
@@ -990,7 +1001,7 @@ export function Tuning() {
|
||||
</div>
|
||||
|
||||
${() => state.status?.isOnroad ? html`
|
||||
<p class="longManeuverError">FTM analysis is offroad-only. Stop the car and go offroad before starting a run.</p>
|
||||
<p class="longManeuverError">FLM analysis is offroad-only. Stop the car and go offroad before starting a run.</p>
|
||||
` : ""}
|
||||
|
||||
${() => state.workspace?.activeTrial?.rollbackAvailable === false ? html`
|
||||
@@ -1005,9 +1016,9 @@ export function Tuning() {
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
<div class="ftmTwoColumn">
|
||||
<section class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<div class="flmTwoColumn">
|
||||
<section class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h3>Local Routes</h3>
|
||||
<p class="longManeuverMuted">
|
||||
@@ -1021,24 +1032,33 @@ export function Tuning() {
|
||||
${() => state.routeTotal ? html`<p class="longManeuverMuted">Route index: ${state.routeProgress}/${state.routeTotal}</p>` : ""}
|
||||
${() => state.truncatedRoutes ? html`<p class="longManeuverMuted">Showing the first ${MAX_RENDERED_ROUTES} routes only.</p>` : ""}
|
||||
|
||||
<div class="ftmRouteList">
|
||||
<div class="flmRouteList">
|
||||
${() => sortedRoutes().map((route) => html`
|
||||
<label class="ftmRouteItem">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked="${() => state.selectedRoutes.includes(route.name)}"
|
||||
@change="${() => toggleRouteSelection(route.name)}" />
|
||||
<span>
|
||||
<strong>${route.timestampLabel}</strong>
|
||||
<small>${route.name}</small>
|
||||
</span>
|
||||
</label>
|
||||
<div class="flmRouteRow">
|
||||
<label class="flmRouteItem">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked="${() => state.selectedRoutes.includes(route.name)}"
|
||||
@change="${() => toggleRouteSelection(route.name)}" />
|
||||
<span>
|
||||
<strong>${route.timestampLabel}</strong>
|
||||
<small>${route.name}</small>
|
||||
</span>
|
||||
</label>
|
||||
${() => connectRouteUrl(route.name) ? html`
|
||||
<a
|
||||
class="flmConnectLink"
|
||||
href="${connectRouteUrl(route.name)}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Connect</a>
|
||||
` : ""}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<section class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h3>Workspace</h3>
|
||||
</div>
|
||||
@@ -1051,19 +1071,19 @@ export function Tuning() {
|
||||
</div>
|
||||
${() => state.loadingWorkspace ? html`<p class="longManeuverMuted">Loading workspace...</p>` : ""}
|
||||
<p class="longManeuverMuted">
|
||||
Recent reports stay on-device under <code>/data/galaxy/ftm</code>. Loading a report refreshes the suggestion and trial view below.
|
||||
Recent reports stay on-device under <code>/data/galaxy/flm</code>. Loading a report refreshes the suggestion and trial view below.
|
||||
</p>
|
||||
<div class="ftmWorkspaceList">
|
||||
<div class="flmWorkspaceList">
|
||||
${() => (state.workspace?.reports || []).length
|
||||
? state.workspace.reports.map((report) => html`
|
||||
<div class="ftmWorkspaceRow">
|
||||
<button class="ftmWorkspaceItem" @click="${() => loadReport(report.reportId)}">
|
||||
<div class="flmWorkspaceRow">
|
||||
<button class="flmWorkspaceItem" @click="${() => loadReport(report.reportId)}">
|
||||
<strong>${report.carFingerprint || "Unknown car"}</strong>
|
||||
<span>${(report.routeNames || []).join(", ")}</span>
|
||||
<small>${formatTimestamp(report.createdAt ? new Date(report.createdAt * 1000).toISOString() : "")}</small>
|
||||
</button>
|
||||
<button
|
||||
class="longManeuverButton danger ftmWorkspaceDelete"
|
||||
class="longManeuverButton danger flmWorkspaceDelete"
|
||||
disabled="${() => state.runningAction}"
|
||||
@click="${() => deleteReport(report.reportId)}">
|
||||
Delete
|
||||
@@ -1076,8 +1096,8 @@ export function Tuning() {
|
||||
</div>
|
||||
|
||||
${() => state.report ? html`
|
||||
<section class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<section class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h3>Report Summary</h3>
|
||||
</div>
|
||||
@@ -1107,12 +1127,12 @@ export function Tuning() {
|
||||
|
||||
${() => renderTuneComparison()}
|
||||
|
||||
<div class="ftmFindings">
|
||||
<div class="flmFindings">
|
||||
${reportPaths().map((path) => renderPathSummary(path))}
|
||||
</div>
|
||||
|
||||
${() => (state.report.warnings || []).length ? html`
|
||||
<div class="ftmCardSubsection">
|
||||
<div class="flmCardSubsection">
|
||||
<h4>Warnings</h4>
|
||||
<ul>
|
||||
${(state.report.warnings || []).map((warning) => html`<li>${warning}</li>`)}
|
||||
@@ -1121,7 +1141,7 @@ export function Tuning() {
|
||||
` : ""}
|
||||
|
||||
${() => (state.report.addTheseParametersAndStartHere || []).length ? html`
|
||||
<div class="ftmCardSubsection">
|
||||
<div class="flmCardSubsection">
|
||||
<h4>Add These Parameters And Start Here</h4>
|
||||
<ul>
|
||||
${(state.report.addTheseParametersAndStartHere || []).map((line) => html`<li>${line}</li>`)}
|
||||
@@ -1130,8 +1150,8 @@ export function Tuning() {
|
||||
` : ""}
|
||||
</section>
|
||||
|
||||
<section class="ftmCard">
|
||||
<div class="ftmCardHeader">
|
||||
<section class="flmCard">
|
||||
<div class="flmCardHeader">
|
||||
<div>
|
||||
<h3>Active Findings: ${primaryPath()?.title || "Recommendations"}</h3>
|
||||
<p class="longManeuverMuted">
|
||||
@@ -1148,21 +1168,21 @@ export function Tuning() {
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
class="ftmNotes"
|
||||
class="flmNotes"
|
||||
placeholder="Optional tuning notes"
|
||||
@input="${(event) => { state.feedbackNotes = event.target.value }}">${() => state.feedbackNotes}</textarea>
|
||||
|
||||
<div class="ftmFindings">
|
||||
<div class="flmFindings">
|
||||
${((primaryPath()?.suggestions) || []).map((suggestion) => renderSuggestion(suggestion))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="ftmCard">
|
||||
<section class="flmCard">
|
||||
<h3>Trial Profiles</h3>
|
||||
<p class="longManeuverMuted">
|
||||
Apply one bounded profile at a time. Revert restores the exact advanced-lateral and FTM state that existed before the trial.
|
||||
Apply one bounded profile at a time. Revert restores the exact advanced-lateral and FLM state that existed before the trial.
|
||||
</p>
|
||||
<div class="ftmFindings">
|
||||
<div class="flmFindings">
|
||||
${reportPaths().length
|
||||
? reportPaths().map((path) => html`
|
||||
<div>
|
||||
@@ -1177,7 +1197,7 @@ export function Tuning() {
|
||||
</div>
|
||||
</section>
|
||||
` : html`
|
||||
<section class="ftmCard">
|
||||
<section class="flmCard">
|
||||
<h3>No Active Report</h3>
|
||||
<p class="longManeuverMuted">
|
||||
Select local routes, run analysis, or open one of the saved reports from the workspace panel.
|
||||
|
||||
+272
-201
File diff suppressed because it is too large
Load Diff
@@ -34,18 +34,18 @@
|
||||
<link rel="stylesheet" href="/assets/components/tools/speed_limits.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/theme_maker.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/testing_ground.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/tuning.css?v=ftm-workspace-6">
|
||||
<link rel="stylesheet" href="/assets/components/tools/tuning.css?v=flm-workspace-6">
|
||||
<link rel="stylesheet" href="/assets/components/tools/troubleshoot.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/tmux.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/toggles.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/update_manager.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/device_settings.css?v=ftm-overrides-1">
|
||||
<link rel="stylesheet" href="/assets/components/tools/device_settings.css?v=flm-overrides-1">
|
||||
<link rel="stylesheet" href="/assets/components/tools/galaxy.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/longitudinal_maneuvers.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/tsk_manager.css">
|
||||
|
||||
<script type="module">
|
||||
import("/assets/components/router.js?v=ftm-workspace-1").catch((err) => {
|
||||
import("/assets/components/router.js?v=flm-workspace-1").catch((err) => {
|
||||
console.error("[the_galaxy] bootstrap failed", err);
|
||||
const target = document.getElementById("app") || document.body;
|
||||
const pre = document.createElement("pre");
|
||||
|
||||
+123
-103
@@ -9,7 +9,7 @@ from types import ModuleType, SimpleNamespace
|
||||
import pytest
|
||||
|
||||
|
||||
MODULE_PATH = Path(__file__).resolve().parents[1] / "ftm_workspace.py"
|
||||
MODULE_PATH = Path(__file__).resolve().parents[1] / "flm_workspace.py"
|
||||
|
||||
|
||||
def _simple_module(name, **attrs):
|
||||
@@ -19,7 +19,7 @@ def _simple_module(name, **attrs):
|
||||
return module
|
||||
|
||||
|
||||
def _install_ftm_import_stubs(tmp_path):
|
||||
def _install_flm_import_stubs(tmp_path):
|
||||
class FakeParams:
|
||||
_store = {}
|
||||
|
||||
@@ -80,7 +80,7 @@ def _install_ftm_import_stubs(tmp_path):
|
||||
KP=1.0,
|
||||
)
|
||||
|
||||
def normalize_ftm_overrides(payload):
|
||||
def normalize_flm_overrides(payload):
|
||||
if isinstance(payload, str):
|
||||
payload = json.loads(payload)
|
||||
payload = payload or {}
|
||||
@@ -104,10 +104,10 @@ def _install_ftm_import_stubs(tmp_path):
|
||||
|
||||
sys.modules["openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes"] = _simple_module(
|
||||
"openpilot.selfdrive.controls.lib.latcontrol_vehicle_tunes",
|
||||
FTM_FRICTION_SPEED_KNOTS=[0.0, 5.0, 10.0, 15.0, 25.0],
|
||||
get_ftm_capabilities=lambda *args, **kwargs: {"richProfileKey": "hyundai_ioniq_6", "frictionFamily": "hkg_canfd"},
|
||||
get_ftm_rich_profile_key=lambda *args, **kwargs: "hyundai_ioniq_6",
|
||||
get_ftm_supported_vehicle_knobs=lambda: {
|
||||
FLM_FRICTION_SPEED_KNOTS=[0.0, 5.0, 10.0, 15.0, 25.0],
|
||||
get_flm_capabilities=lambda *args, **kwargs: {"richProfileKey": "hyundai_ioniq_6", "frictionFamily": "hkg_canfd"},
|
||||
get_flm_rich_profile_key=lambda *args, **kwargs: "hyundai_ioniq_6",
|
||||
get_flm_supported_vehicle_knobs=lambda: {
|
||||
"hyundai_ioniq_6.ff_gain_left": {"min": 0.0, "max": 0.6, "precision": 0.001, "defaultValue": 0.1, "profile": "hyundai_ioniq_6"},
|
||||
"hyundai_ioniq_6.ff_gain_right": {"min": 0.0, "max": 0.6, "precision": 0.001, "defaultValue": 0.12, "profile": "hyundai_ioniq_6"},
|
||||
"hyundai_ioniq_6.turn_in_boost_left": {"min": 0.4, "max": 2.8, "precision": 0.001, "defaultValue": 1.64, "profile": "hyundai_ioniq_6"},
|
||||
@@ -120,7 +120,7 @@ def _install_ftm_import_stubs(tmp_path):
|
||||
get_gm_base_friction_threshold=lambda v_ego: 0.20 + (0.001 * float(v_ego)),
|
||||
get_hkg_canfd_base_friction_threshold=lambda v_ego: 0.39 + (0.001 * float(v_ego)),
|
||||
get_standard_friction_threshold=lambda v_ego: 0.30 + (0.001 * float(v_ego)),
|
||||
normalize_ftm_overrides=normalize_ftm_overrides,
|
||||
normalize_flm_overrides=normalize_flm_overrides,
|
||||
)
|
||||
sys.modules["openpilot.system.hardware"] = _simple_module("openpilot.system.hardware", PC=True)
|
||||
sys.modules["openpilot.system.hardware.hw"] = _simple_module(
|
||||
@@ -136,9 +136,9 @@ def _install_ftm_import_stubs(tmp_path):
|
||||
return FakeParams
|
||||
|
||||
|
||||
def _load_ftm_workspace_module(tmp_path):
|
||||
fake_params_cls = _install_ftm_import_stubs(tmp_path)
|
||||
module_name = f"test_ftm_workspace_{hash(tmp_path)}"
|
||||
def _load_flm_workspace_module(tmp_path):
|
||||
fake_params_cls = _install_flm_import_stubs(tmp_path)
|
||||
module_name = f"test_flm_workspace_{hash(tmp_path)}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, MODULE_PATH)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
@@ -173,11 +173,31 @@ def _sample(module, **kwargs):
|
||||
roll_deg=0.0,
|
||||
)
|
||||
base.update(kwargs)
|
||||
return module.FTMSample(**base)
|
||||
return module.FLMSample(**base)
|
||||
|
||||
|
||||
def test_legacy_workspace_is_migrated_to_flm(tmp_path):
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
legacy_name = "".join(("f", "t", "m"))
|
||||
legacy_root = tmp_path / "starpilot" / "data" / "galaxy" / legacy_name
|
||||
legacy_report = legacy_root / "reports" / "legacy.json"
|
||||
legacy_report.parent.mkdir(parents=True)
|
||||
legacy_report.write_text(json.dumps({
|
||||
"reportId": "legacy",
|
||||
f"{legacy_name}Overrides": {"vehicleKnobs": {"generic.ff_gain_left": 0.1}},
|
||||
"profileLabel": legacy_name.upper(),
|
||||
}), encoding="utf-8")
|
||||
|
||||
workspace = module.ensure_flm_workspace()
|
||||
migrated = json.loads((workspace["reports"] / "legacy.json").read_text(encoding="utf-8"))
|
||||
|
||||
assert not legacy_root.exists()
|
||||
assert migrated["flmOverrides"]["vehicleKnobs"]["generic.ff_gain_left"] == pytest.approx(0.1)
|
||||
assert migrated["profileLabel"] == "FLM"
|
||||
|
||||
|
||||
def test_classify_torque_samples_detects_center_chatter(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
samples = []
|
||||
for idx in range(60):
|
||||
angle = 0.75 * math.sin(idx * 0.9)
|
||||
@@ -199,7 +219,7 @@ def test_classify_torque_samples_detects_center_chatter(tmp_path):
|
||||
|
||||
|
||||
def test_plot_context_stops_at_ineligible_samples(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
samples = [_sample(module, t=idx * 0.1, desired_la=idx * 0.01, actual_la=idx * 0.009) for idx in range(20)]
|
||||
eligibility = [True] * len(samples)
|
||||
eligibility[5] = False
|
||||
@@ -220,7 +240,7 @@ def test_plot_context_stops_at_ineligible_samples(tmp_path):
|
||||
|
||||
|
||||
def test_analysis_eligibility_masks_driver_override_with_settle_buffer(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
samples = [
|
||||
_sample(module, t=idx * 0.1, steering_pressed=(idx == 20))
|
||||
for idx in range(50)
|
||||
@@ -235,7 +255,7 @@ def test_analysis_eligibility_masks_driver_override_with_settle_buffer(tmp_path)
|
||||
|
||||
|
||||
def test_stock_param_state_captures_generic_and_rich_defaults(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
torque_tune = SimpleNamespace(friction=0.09, latAccelFactor=3.0)
|
||||
lateral_tuning = SimpleNamespace(which=lambda: "torque", torque=torque_tune)
|
||||
CP = SimpleNamespace(lateralTuning=lateral_tuning, steerActuatorDelay=0.1, steerRatio=14.26)
|
||||
@@ -247,12 +267,12 @@ def test_stock_param_state_captures_generic_and_rich_defaults(tmp_path):
|
||||
assert stock["UseAutoSteerDelay"] is True
|
||||
assert stock["SteerDelay"] == pytest.approx(0.3)
|
||||
assert stock["SteerRatio"] == pytest.approx(14.26)
|
||||
assert len(stock["FTMBaseFrictionThresholds"]["hkg_canfd"]["values"]) == 5
|
||||
assert stock["FTMVehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(1.64)
|
||||
assert len(stock["FLMBaseFrictionThresholds"]["hkg_canfd"]["values"]) == 5
|
||||
assert stock["FLMVehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(1.64)
|
||||
|
||||
|
||||
def test_classify_torque_samples_does_not_bridge_driver_override(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
samples = []
|
||||
for idx in range(80):
|
||||
samples.append(_sample(
|
||||
@@ -271,7 +291,7 @@ def test_classify_torque_samples_does_not_bridge_driver_override(tmp_path):
|
||||
|
||||
|
||||
def test_build_suggestions_prefers_rich_low_speed_turn_in_knob(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "low_speed_unwillingness",
|
||||
"dimensionId": "low_speed_unwillingness:left:low",
|
||||
@@ -291,7 +311,7 @@ def test_build_suggestions_prefers_rich_low_speed_turn_in_knob(tmp_path):
|
||||
|
||||
|
||||
def test_build_suggestions_baseline_prefers_generic_lat_accel_for_understeer(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "understeer",
|
||||
"dimensionId": "understeer:left:mid",
|
||||
@@ -312,7 +332,7 @@ def test_build_suggestions_baseline_prefers_generic_lat_accel_for_understeer(tmp
|
||||
|
||||
|
||||
def test_build_suggestions_baseline_respects_asymmetric_nonlinear_map(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "understeer",
|
||||
"dimensionId": "understeer:right:mid",
|
||||
@@ -342,7 +362,7 @@ def test_build_suggestions_baseline_respects_asymmetric_nonlinear_map(tmp_path):
|
||||
|
||||
|
||||
def test_build_suggestions_rebases_rich_knob_against_active_override(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "low_speed_unwillingness",
|
||||
"dimensionId": "low_speed_unwillingness:left:low",
|
||||
@@ -356,7 +376,7 @@ def test_build_suggestions_rebases_rich_knob_against_active_override(tmp_path):
|
||||
current = {
|
||||
"SteerLatAccel": 1.8,
|
||||
"SteerFriction": 0.2,
|
||||
"FTMActiveOverrides": {
|
||||
"FLMActiveOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {
|
||||
@@ -374,7 +394,7 @@ def test_build_suggestions_rebases_rich_knob_against_active_override(tmp_path):
|
||||
|
||||
|
||||
def test_build_suggestions_prefers_ioniq_6_curvy_trim_for_mid_speed_turn_in(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "oversteer",
|
||||
"dimensionId": "oversteer:left:fast",
|
||||
@@ -395,7 +415,7 @@ def test_build_suggestions_prefers_ioniq_6_curvy_trim_for_mid_speed_turn_in(tmp_
|
||||
|
||||
|
||||
def test_build_suggestions_rebases_friction_curve_against_active_override(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summary = {
|
||||
"bucket": "center_chatter",
|
||||
"dimensionId": "center_chatter:center:highway",
|
||||
@@ -410,7 +430,7 @@ def test_build_suggestions_rebases_friction_curve_against_active_override(tmp_pa
|
||||
current = {
|
||||
"SteerLatAccel": 1.8,
|
||||
"SteerFriction": 0.2,
|
||||
"FTMActiveOverrides": {
|
||||
"FLMActiveOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {
|
||||
"standard": {
|
||||
@@ -431,7 +451,7 @@ def test_build_suggestions_rebases_friction_curve_against_active_override(tmp_pa
|
||||
|
||||
|
||||
def test_select_primary_tuning_path_prefers_baseline_for_broad_mismatch(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summaries = [
|
||||
{"bucket": "understeer", "severity": 1.0},
|
||||
{"bucket": "center_chatter", "severity": 0.9},
|
||||
@@ -446,7 +466,7 @@ def test_select_primary_tuning_path_prefers_baseline_for_broad_mismatch(tmp_path
|
||||
|
||||
|
||||
def test_select_primary_tuning_path_prefers_cleanup_for_localized_issue(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summaries = [
|
||||
{"bucket": "notchy_mid_curve", "severity": 0.7},
|
||||
{"bucket": "center_chatter", "severity": 0.55},
|
||||
@@ -459,7 +479,7 @@ def test_select_primary_tuning_path_prefers_cleanup_for_localized_issue(tmp_path
|
||||
|
||||
|
||||
def test_select_primary_tuning_path_vetoes_baseline_when_global_fit_is_strong(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summaries = [
|
||||
{
|
||||
"bucket": "late_turn_in",
|
||||
@@ -478,7 +498,7 @@ def test_select_primary_tuning_path_vetoes_baseline_when_global_fit_is_strong(tm
|
||||
|
||||
|
||||
def test_conflicting_summary_resolution_keeps_dominant_direction(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
summaries = [
|
||||
{"bucket": "early_turn_in", "severity": 1.34, "evidence": {"directionBias": "right", "speedBand": "mid", "eventCount": 1}},
|
||||
{"bucket": "late_turn_in", "severity": 1.03, "evidence": {"directionBias": "right", "speedBand": "mid", "eventCount": 18}},
|
||||
@@ -489,7 +509,7 @@ def test_conflicting_summary_resolution_keeps_dominant_direction(tmp_path):
|
||||
|
||||
|
||||
def test_build_trial_profiles_suppresses_ignored_dimensions(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
suggestions = [
|
||||
{
|
||||
"dimensionId": "center_chatter:center:highway",
|
||||
@@ -518,11 +538,11 @@ def test_build_trial_profiles_suppresses_ignored_dimensions(tmp_path):
|
||||
assert profiles
|
||||
assert profiles[0]["genericParams"]["ForceAutoTuneOff"] is True
|
||||
assert profiles[0]["genericParams"]["SteerLatAccel"] > 1.6
|
||||
assert profiles[0]["ftmOverrides"] == {}
|
||||
assert profiles[0]["flmOverrides"] == {}
|
||||
|
||||
|
||||
def test_build_trial_profiles_returns_none_when_every_dimension_is_ignored(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
suggestion = {
|
||||
"dimensionId": "understeer:left:mid",
|
||||
"severity": 0.8,
|
||||
@@ -545,7 +565,7 @@ def test_build_trial_profiles_returns_none_when_every_dimension_is_ignored(tmp_p
|
||||
|
||||
|
||||
def test_merge_primary_adjustments_averages_conflicting_deltas(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
suggestions = [
|
||||
{
|
||||
"severity": 1.0,
|
||||
@@ -575,7 +595,7 @@ def test_merge_primary_adjustments_averages_conflicting_deltas(tmp_path):
|
||||
|
||||
|
||||
def test_merge_primary_adjustments_disables_auto_delay_for_manual_delay_trial(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
suggestions = [{
|
||||
"severity": 1.0,
|
||||
"primaryAdjustmentRaw": {
|
||||
@@ -595,8 +615,8 @@ def test_merge_primary_adjustments_disables_auto_delay_for_manual_delay_trial(tm
|
||||
|
||||
|
||||
def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-apply"
|
||||
profile_id = f"{report_id}:recommended"
|
||||
profile = {
|
||||
@@ -610,7 +630,7 @@ def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
"ForceAutoTuneOff": True,
|
||||
"ForceAutoTune": False,
|
||||
},
|
||||
"ftmOverrides": {
|
||||
"flmOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {
|
||||
@@ -625,13 +645,13 @@ def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
"ForceAutoTune": True,
|
||||
"ForceAutoTuneOff": False,
|
||||
"SteerLatAccel": 1.5,
|
||||
"FTMActiveProfileId": "",
|
||||
"FTMActiveOverrides": {
|
||||
"FLMActiveProfileId": "",
|
||||
"FLMActiveOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {"hyundai_ioniq_6.unwind_taper_left": 0.55},
|
||||
},
|
||||
"FTMTrialApplied": False,
|
||||
"FLMTrialApplied": False,
|
||||
}
|
||||
|
||||
result = module.apply_trial_profile(report_id, profile_id)
|
||||
@@ -643,24 +663,24 @@ def test_apply_and_revert_trial_profile_round_trip(tmp_path):
|
||||
assert active_snapshot["appliedVehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert active_snapshot["params"]["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.9)
|
||||
assert fake_params_cls._store["FTMActiveProfileId"] == profile_id
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is True
|
||||
assert fake_params_cls._store["FTMTrialBaseline"]["params"]["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
assert fake_params_cls._store["FLMActiveProfileId"] == profile_id
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is True
|
||||
assert fake_params_cls._store["FLMTrialBaseline"]["params"]["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
|
||||
revert_result = module.revert_trial_profile()
|
||||
assert revert_result["snapshot"]["profileId"] == profile_id
|
||||
assert fake_params_cls._store["AdvancedLateralTune"] is False
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert "FTMTrialBaseline" not in fake_params_cls._store
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
assert "FLMTrialBaseline" not in fake_params_cls._store
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.55)
|
||||
|
||||
|
||||
def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
first_report_id = "report-first"
|
||||
first_profile_id = f"{first_report_id}:cleanup_pass:recommended"
|
||||
second_report_id = "report-second"
|
||||
@@ -671,7 +691,7 @@ def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
"pathKey": "cleanup_pass",
|
||||
"pathLabel": "Cleanup Pass",
|
||||
"genericParams": {"AdvancedLateralTune": True, "SteerLatAccel": 1.8},
|
||||
"ftmOverrides": {
|
||||
"flmOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {"hyundai_ioniq_6.turn_in_boost_left": 0.08},
|
||||
@@ -683,7 +703,7 @@ def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
"pathKey": "cleanup_pass",
|
||||
"pathLabel": "Cleanup Pass",
|
||||
"genericParams": {"AdvancedLateralTune": True, "SteerLatAccel": 1.9},
|
||||
"ftmOverrides": {
|
||||
"flmOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {"hyundai_ioniq_6.unwind_taper_left": 0.62},
|
||||
@@ -694,9 +714,9 @@ def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
fake_params_cls._store = {
|
||||
"AdvancedLateralTune": False,
|
||||
"SteerLatAccel": 1.5,
|
||||
"FTMActiveProfileId": "",
|
||||
"FTMActiveOverrides": {},
|
||||
"FTMTrialApplied": False,
|
||||
"FLMActiveProfileId": "",
|
||||
"FLMActiveOverrides": {},
|
||||
"FLMTrialApplied": False,
|
||||
}
|
||||
|
||||
module.apply_trial_profile(first_report_id, first_profile_id)
|
||||
@@ -705,23 +725,23 @@ def test_repeated_trial_revisions_revert_to_original_baseline(tmp_path):
|
||||
active_snapshot = json.loads((workspace["snapshots"] / "active.json").read_text(encoding="utf-8"))
|
||||
assert active_snapshot["revisionCount"] == 2
|
||||
assert active_snapshot["params"]["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert active_snapshot["params"]["FTMTrialApplied"] is False
|
||||
assert active_snapshot["params"]["FLMTrialApplied"] is False
|
||||
assert active_snapshot["appliedGenericParams"]["SteerLatAccel"] == pytest.approx(1.9)
|
||||
assert active_snapshot["appliedVehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert active_snapshot["appliedVehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.62)
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.62)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.turn_in_boost_left"] == pytest.approx(0.08)
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["hyundai_ioniq_6.unwind_taper_left"] == pytest.approx(0.62)
|
||||
|
||||
module.revert_trial_profile()
|
||||
assert fake_params_cls._store["AdvancedLateralTune"] is False
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FTMActiveOverrides"] == {}
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FLMActiveOverrides"] == {}
|
||||
|
||||
|
||||
def test_orphaned_previous_revision_can_recover_its_baseline(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-recovery"
|
||||
profile_id = f"{report_id}:cleanup_pass:recommended"
|
||||
profile = {
|
||||
@@ -730,15 +750,15 @@ def test_orphaned_previous_revision_can_recover_its_baseline(tmp_path):
|
||||
"pathKey": "cleanup_pass",
|
||||
"pathLabel": "Cleanup Pass",
|
||||
"genericParams": {"AdvancedLateralTune": True, "SteerLatAccel": 1.8},
|
||||
"ftmOverrides": {},
|
||||
"flmOverrides": {},
|
||||
}
|
||||
(workspace["profiles"] / f"{report_id}.json").write_text(json.dumps([profile]), encoding="utf-8")
|
||||
fake_params_cls._store = {
|
||||
"AdvancedLateralTune": False,
|
||||
"SteerLatAccel": 1.5,
|
||||
"FTMActiveProfileId": "",
|
||||
"FTMActiveOverrides": {},
|
||||
"FTMTrialApplied": False,
|
||||
"FLMActiveProfileId": "",
|
||||
"FLMActiveOverrides": {},
|
||||
"FLMTrialApplied": False,
|
||||
}
|
||||
module.apply_trial_profile(report_id, profile_id)
|
||||
(workspace["snapshots"] / "active.json").unlink()
|
||||
@@ -749,27 +769,27 @@ def test_orphaned_previous_revision_can_recover_its_baseline(tmp_path):
|
||||
|
||||
module.revert_trial_profile()
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
|
||||
|
||||
def test_persistent_baseline_recovers_when_snapshot_files_are_missing(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-persistent-recovery"
|
||||
profile_id = f"{report_id}:cleanup_pass:recommended"
|
||||
profile = {
|
||||
"id": profile_id,
|
||||
"label": "Recommended",
|
||||
"genericParams": {"AdvancedLateralTune": True, "SteerLatAccel": 1.8},
|
||||
"ftmOverrides": {},
|
||||
"flmOverrides": {},
|
||||
}
|
||||
(workspace["profiles"] / f"{report_id}.json").write_text(json.dumps([profile]), encoding="utf-8")
|
||||
fake_params_cls._store = {
|
||||
"AdvancedLateralTune": False,
|
||||
"SteerLatAccel": 1.5,
|
||||
"FTMActiveProfileId": "",
|
||||
"FTMActiveOverrides": {},
|
||||
"FTMTrialApplied": False,
|
||||
"FLMActiveProfileId": "",
|
||||
"FLMActiveOverrides": {},
|
||||
"FLMTrialApplied": False,
|
||||
}
|
||||
|
||||
module.apply_trial_profile(report_id, profile_id)
|
||||
@@ -782,12 +802,12 @@ def test_persistent_baseline_recovers_when_snapshot_files_are_missing(tmp_path):
|
||||
|
||||
module.revert_trial_profile()
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
|
||||
|
||||
def test_legacy_orphan_recovers_baseline_from_source_report(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-source-recovery"
|
||||
profile_id = f"{report_id}:baseline_fix:assertive"
|
||||
(workspace["reports"] / f"{report_id}.json").write_text(json.dumps({
|
||||
@@ -796,17 +816,17 @@ def test_legacy_orphan_recovers_baseline_from_source_report(tmp_path):
|
||||
"currentParams": {
|
||||
"AdvancedLateralTune": False,
|
||||
"SteerLatAccel": 1.5,
|
||||
"FTMActiveProfileId": "",
|
||||
"FTMActiveOverrides": {},
|
||||
"FTMTrialApplied": False,
|
||||
"FLMActiveProfileId": "",
|
||||
"FLMActiveOverrides": {},
|
||||
"FLMTrialApplied": False,
|
||||
},
|
||||
}), encoding="utf-8")
|
||||
fake_params_cls._store = {
|
||||
"AdvancedLateralTune": True,
|
||||
"SteerLatAccel": 1.9,
|
||||
"FTMActiveProfileId": profile_id,
|
||||
"FTMActiveOverrides": {},
|
||||
"FTMTrialApplied": True,
|
||||
"FLMActiveProfileId": profile_id,
|
||||
"FLMActiveOverrides": {},
|
||||
"FLMTrialApplied": True,
|
||||
}
|
||||
|
||||
active_trial = module.list_workspace()["activeTrial"]
|
||||
@@ -816,18 +836,18 @@ def test_legacy_orphan_recovers_baseline_from_source_report(tmp_path):
|
||||
module.revert_trial_profile()
|
||||
assert fake_params_cls._store["AdvancedLateralTune"] is False
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.5)
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
|
||||
|
||||
def test_irrecoverable_trial_can_keep_current_values_as_new_baseline(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
module.ensure_flm_workspace()
|
||||
fake_params_cls._store = {
|
||||
"AdvancedLateralTune": True,
|
||||
"SteerLatAccel": 1.9,
|
||||
"FTMActiveProfileId": "missing-report:baseline_fix:assertive",
|
||||
"FTMActiveOverrides": {"vehicleKnobs": {"generic.turn_in_boost_left": 0.1}},
|
||||
"FTMTrialApplied": True,
|
||||
"FLMActiveProfileId": "missing-report:baseline_fix:assertive",
|
||||
"FLMActiveOverrides": {"vehicleKnobs": {"generic.turn_in_boost_left": 0.1}},
|
||||
"FLMTrialApplied": True,
|
||||
}
|
||||
|
||||
active_trial = module.list_workspace()["activeTrial"]
|
||||
@@ -835,14 +855,14 @@ def test_irrecoverable_trial_can_keep_current_values_as_new_baseline(tmp_path):
|
||||
|
||||
module.accept_trial_as_baseline()
|
||||
assert fake_params_cls._store["SteerLatAccel"] == pytest.approx(1.9)
|
||||
assert fake_params_cls._store["FTMActiveOverrides"]["vehicleKnobs"]["generic.turn_in_boost_left"] == pytest.approx(0.1)
|
||||
assert fake_params_cls._store["FTMActiveProfileId"] == ""
|
||||
assert fake_params_cls._store["FTMTrialApplied"] is False
|
||||
assert fake_params_cls._store["FLMActiveOverrides"]["vehicleKnobs"]["generic.turn_in_boost_left"] == pytest.approx(0.1)
|
||||
assert fake_params_cls._store["FLMActiveProfileId"] == ""
|
||||
assert fake_params_cls._store["FLMTrialApplied"] is False
|
||||
|
||||
|
||||
def test_workspace_hydrates_display_metadata_for_existing_active_trial(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-existing"
|
||||
profile_id = f"{report_id}:cleanup_pass:recommended"
|
||||
profile = {
|
||||
@@ -851,7 +871,7 @@ def test_workspace_hydrates_display_metadata_for_existing_active_trial(tmp_path)
|
||||
"pathKey": "cleanup_pass",
|
||||
"pathLabel": "Cleanup Pass",
|
||||
"genericParams": {"AdvancedLateralTune": True, "SteerFriction": 0.25},
|
||||
"ftmOverrides": {
|
||||
"flmOverrides": {
|
||||
"schemaVersion": 1,
|
||||
"baseFrictionThresholds": {},
|
||||
"vehicleKnobs": {"hyundai_ioniq_6.ff_gain_left": 0.15},
|
||||
@@ -872,8 +892,8 @@ def test_workspace_hydrates_display_metadata_for_existing_active_trial(tmp_path)
|
||||
|
||||
|
||||
def test_delete_report_removes_saved_artifacts(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-delete"
|
||||
for path in (
|
||||
workspace["reports"] / f"{report_id}.json",
|
||||
@@ -894,12 +914,12 @@ def test_delete_report_removes_saved_artifacts(tmp_path):
|
||||
|
||||
|
||||
def test_delete_report_is_blocked_while_trial_is_active(tmp_path):
|
||||
module, fake_params_cls = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-active-delete"
|
||||
report_path = workspace["reports"] / f"{report_id}.json"
|
||||
report_path.write_text("{}", encoding="utf-8")
|
||||
fake_params_cls._store = {"FTMTrialApplied": True}
|
||||
fake_params_cls._store = {"FLMTrialApplied": True}
|
||||
|
||||
with pytest.raises(RuntimeError, match="Revert or keep"):
|
||||
module.delete_report(report_id)
|
||||
@@ -907,8 +927,8 @@ def test_delete_report_is_blocked_while_trial_is_active(tmp_path):
|
||||
|
||||
|
||||
def test_select_report_path_persists_manual_override(tmp_path):
|
||||
module, _ = _load_ftm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_ftm_workspace()
|
||||
module, _ = _load_flm_workspace_module(tmp_path)
|
||||
workspace = module.ensure_flm_workspace()
|
||||
report_id = "report-path"
|
||||
suggestion_base = {
|
||||
"evidence": {"speedBand": "mixed", "directionBias": "center", "eventCount": 0, "segments": []},
|
||||
@@ -77,7 +77,7 @@ from openpilot.starpilot.common.testing_grounds import (
|
||||
)
|
||||
from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, update_recent_destinations
|
||||
from openpilot.starpilot.system.the_galaxy.factory_reset import remove_path as _run_factory_reset_delete
|
||||
from openpilot.starpilot.system.the_galaxy import ftm_workspace, utilities
|
||||
from openpilot.starpilot.system.the_galaxy import flm_workspace, utilities
|
||||
from openpilot.starpilot.system.the_galaxy.update_recovery import inspect_interrupted_update, public_recovery_status, recover_interrupted_update
|
||||
|
||||
DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
|
||||
@@ -85,6 +85,7 @@ DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL")
|
||||
GITLAB_API = "https://gitlab.com/api/v4"
|
||||
GITLAB_SUBMISSIONS_PROJECT_ID = "71992109"
|
||||
GITLAB_TOKEN = os.environ.get("GITLAB_TOKEN", "")
|
||||
LEGACY_LATERAL_METHOD_API_PREFIX = "/api/" + "".join(("f", "t", "m"))
|
||||
|
||||
GALAXY_DEPS_PATH = "/data/galaxy_deps"
|
||||
LEGACY_GALAXY_DEPS_PATH = "/data/" + "".join(chr(code) for code in (112, 111, 110, 100)) + "_deps"
|
||||
@@ -5115,7 +5116,8 @@ def setup(app):
|
||||
def generate():
|
||||
routes = [(path, name) for path in FOOTAGE_PATHS for name in utilities.get_routes_names(path)]
|
||||
total = len(routes)
|
||||
yield f"data: {json.dumps({'progress': 0, 'total': total})}\n\n"
|
||||
connect_dongle_id = params.get("StockDongleId", encoding="utf-8") or params.get("DongleId", encoding="utf-8") or ""
|
||||
yield f"data: {json.dumps({'progress': 0, 'total': total, 'connectDongleId': connect_dongle_id})}\n\n"
|
||||
|
||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
futures = {executor.submit(utilities.process_route, path, name): (path, name) for path, name in routes}
|
||||
@@ -5685,87 +5687,96 @@ def setup(app):
|
||||
**_serialize_lateral_maneuver_status(status),
|
||||
}), 200
|
||||
|
||||
@app.route("/api/ftm/status", methods=["GET"])
|
||||
def get_ftm_status():
|
||||
workspace = ftm_workspace.list_workspace()
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/status", methods=["GET"])
|
||||
@app.route("/api/flm/status", methods=["GET"])
|
||||
def get_flm_status():
|
||||
workspace = flm_workspace.list_workspace()
|
||||
return jsonify({
|
||||
"isOnroad": params.get_bool("IsOnroad"),
|
||||
"status": ftm_workspace.read_ftm_status(),
|
||||
"status": flm_workspace.read_flm_status(),
|
||||
"activeTrial": workspace.get("activeTrial"),
|
||||
"reports": workspace.get("reports", [])[:10],
|
||||
}), 200
|
||||
|
||||
@app.route("/api/ftm/analyze", methods=["POST"])
|
||||
def start_ftm_analysis():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/analyze", methods=["POST"])
|
||||
@app.route("/api/flm/analyze", methods=["POST"])
|
||||
def start_flm_analysis():
|
||||
if params.get_bool("IsOnroad"):
|
||||
return jsonify({"error": "FTM analysis can only run offroad."}), 409
|
||||
return jsonify({"error": "FLM analysis can only run offroad."}), 409
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
route_names = [str(route).strip() for route in data.get("routes", []) if str(route).strip()]
|
||||
if not route_names:
|
||||
return jsonify({"error": "No routes were selected."}), 400
|
||||
|
||||
started = ftm_workspace.start_ftm_background_analysis(route_names, FOOTAGE_PATHS)
|
||||
started = flm_workspace.start_flm_background_analysis(route_names, FOOTAGE_PATHS)
|
||||
if not started:
|
||||
return jsonify({"error": "Failed to start FTM analysis."}), 500
|
||||
return jsonify({"error": "Failed to start FLM analysis."}), 500
|
||||
|
||||
return jsonify({
|
||||
"message": f"Started FTM analysis for {len(route_names[:ftm_workspace.FTM_ANALYZER_ROUTE_LIMIT])} route(s).",
|
||||
"status": ftm_workspace.read_ftm_status(),
|
||||
"message": f"Started FLM analysis for {len(route_names[:flm_workspace.FLM_ANALYZER_ROUTE_LIMIT])} route(s).",
|
||||
"status": flm_workspace.read_flm_status(),
|
||||
}), 200
|
||||
|
||||
@app.route("/api/ftm/analyze/stop", methods=["POST"])
|
||||
def stop_ftm_analysis():
|
||||
stopped = ftm_workspace.stop_ftm_background_analysis()
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/analyze/stop", methods=["POST"])
|
||||
@app.route("/api/flm/analyze/stop", methods=["POST"])
|
||||
def stop_flm_analysis():
|
||||
stopped = flm_workspace.stop_flm_background_analysis()
|
||||
return jsonify({
|
||||
"message": "Stopped FTM analysis." if stopped else "No active FTM analysis was running.",
|
||||
"message": "Stopped FLM analysis." if stopped else "No active FLM analysis was running.",
|
||||
"stopped": bool(stopped),
|
||||
"status": ftm_workspace.read_ftm_status(),
|
||||
"status": flm_workspace.read_flm_status(),
|
||||
}), 200
|
||||
|
||||
@app.route("/api/ftm/report/<report_id>", methods=["GET"])
|
||||
def get_ftm_report(report_id):
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/report/<report_id>", methods=["GET"])
|
||||
@app.route("/api/flm/report/<report_id>", methods=["GET"])
|
||||
def get_flm_report(report_id):
|
||||
try:
|
||||
return jsonify(ftm_workspace.load_report(report_id)), 200
|
||||
return jsonify(flm_workspace.load_report(report_id)), 200
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "FTM report not found."}), 404
|
||||
return jsonify({"error": "FLM report not found."}), 404
|
||||
|
||||
@app.route("/api/ftm/report/<report_id>", methods=["DELETE"])
|
||||
def delete_ftm_report(report_id):
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/report/<report_id>", methods=["DELETE"])
|
||||
@app.route("/api/flm/report/<report_id>", methods=["DELETE"])
|
||||
def delete_flm_report(report_id):
|
||||
try:
|
||||
return jsonify(ftm_workspace.delete_report(report_id)), 200
|
||||
return jsonify(flm_workspace.delete_report(report_id)), 200
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "FTM report not found."}), 404
|
||||
return jsonify({"error": "FLM report not found."}), 404
|
||||
except RuntimeError as error:
|
||||
return jsonify({"error": str(error)}), 409
|
||||
|
||||
@app.route("/api/ftm/report/<report_id>/path", methods=["POST"])
|
||||
def select_ftm_report_path(report_id):
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/report/<report_id>/path", methods=["POST"])
|
||||
@app.route("/api/flm/report/<report_id>/path", methods=["POST"])
|
||||
def select_flm_report_path(report_id):
|
||||
data = request.get_json(silent=True) or {}
|
||||
path_key = str(data.get("pathKey") or "").strip()
|
||||
if not path_key:
|
||||
return jsonify({"error": "pathKey is required."}), 400
|
||||
|
||||
try:
|
||||
return jsonify(ftm_workspace.select_report_path(report_id, path_key)), 200
|
||||
return jsonify(flm_workspace.select_report_path(report_id, path_key)), 200
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "FTM report not found."}), 404
|
||||
return jsonify({"error": "FLM report not found."}), 404
|
||||
except ValueError as error:
|
||||
return jsonify({"error": str(error)}), 400
|
||||
|
||||
@app.route("/api/ftm/workspace", methods=["GET"])
|
||||
def get_ftm_workspace():
|
||||
return jsonify(ftm_workspace.list_workspace()), 200
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/workspace", methods=["GET"])
|
||||
@app.route("/api/flm/workspace", methods=["GET"])
|
||||
def get_flm_workspace():
|
||||
return jsonify(flm_workspace.list_workspace()), 200
|
||||
|
||||
@app.route("/api/ftm/workspace/clear", methods=["POST"])
|
||||
def clear_ftm_workspace():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/workspace/clear", methods=["POST"])
|
||||
@app.route("/api/flm/workspace/clear", methods=["POST"])
|
||||
def clear_flm_workspace():
|
||||
try:
|
||||
return jsonify(ftm_workspace.clear_workspace()), 200
|
||||
return jsonify(flm_workspace.clear_workspace()), 200
|
||||
except RuntimeError as error:
|
||||
return jsonify({"error": str(error)}), 409
|
||||
|
||||
@app.route("/api/ftm/trials/apply", methods=["POST"])
|
||||
def apply_ftm_trial():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/trials/apply", methods=["POST"])
|
||||
@app.route("/api/flm/trials/apply", methods=["POST"])
|
||||
def apply_flm_trial():
|
||||
data = request.get_json(silent=True) or {}
|
||||
report_id = str(data.get("reportId") or "").strip()
|
||||
profile_id = str(data.get("profileId") or "").strip()
|
||||
@@ -5773,34 +5784,37 @@ def setup(app):
|
||||
return jsonify({"error": "Both reportId and profileId are required."}), 400
|
||||
|
||||
try:
|
||||
result = ftm_workspace.apply_trial_profile(report_id, profile_id)
|
||||
result = flm_workspace.apply_trial_profile(report_id, profile_id)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "FTM profile not found."}), 404
|
||||
return jsonify({"error": "FLM profile not found."}), 404
|
||||
except RuntimeError as error:
|
||||
return jsonify({"error": str(error)}), 409
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
@app.route("/api/ftm/trials/revert", methods=["POST"])
|
||||
def revert_ftm_trial():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/trials/revert", methods=["POST"])
|
||||
@app.route("/api/flm/trials/revert", methods=["POST"])
|
||||
def revert_flm_trial():
|
||||
try:
|
||||
result = ftm_workspace.revert_trial_profile()
|
||||
result = flm_workspace.revert_trial_profile()
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "No active FTM trial snapshot was found."}), 404
|
||||
return jsonify({"error": "No active FLM trial snapshot was found."}), 404
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
@app.route("/api/ftm/trials/accept", methods=["POST"])
|
||||
def accept_ftm_trial():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/trials/accept", methods=["POST"])
|
||||
@app.route("/api/flm/trials/accept", methods=["POST"])
|
||||
def accept_flm_trial():
|
||||
try:
|
||||
result = ftm_workspace.accept_trial_as_baseline()
|
||||
result = flm_workspace.accept_trial_as_baseline()
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "No active FTM trial was found."}), 404
|
||||
return jsonify({"error": "No active FLM trial was found."}), 404
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
@app.route("/api/ftm/feedback", methods=["POST"])
|
||||
def save_ftm_feedback():
|
||||
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/feedback", methods=["POST"])
|
||||
@app.route("/api/flm/feedback", methods=["POST"])
|
||||
def save_flm_feedback():
|
||||
data = request.get_json(silent=True) or {}
|
||||
report_id = str(data.get("reportId") or "").strip()
|
||||
if not report_id:
|
||||
@@ -5812,9 +5826,9 @@ def setup(app):
|
||||
"notes": data.get("notes", ""),
|
||||
}
|
||||
try:
|
||||
result = ftm_workspace.record_feedback(report_id, feedback)
|
||||
result = flm_workspace.record_feedback(report_id, feedback)
|
||||
except FileNotFoundError:
|
||||
return jsonify({"error": "FTM report not found."}), 404
|
||||
return jsonify({"error": "FLM report not found."}), 404
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
|
||||
@@ -20,8 +20,16 @@ LAUNCH_PARAM_MIGRATION_MARKER = ".starpilot_launch_param_migrations_v2"
|
||||
BRANCH_DEFAULTS_MIGRATION_MARKER = ".starpilot_branch_defaults_migrations_v1"
|
||||
ACCELERATION_PROFILE_MIGRATION_MARKER = ".starpilot_acceleration_profile_default_v1"
|
||||
USE_OLD_UI_MIGRATION_MARKER = ".starpilot_use_old_ui_migration_v1"
|
||||
LATERAL_METHOD_REBRAND_MIGRATION_MARKER = ".starpilot_lateral_method_rebrand_v1"
|
||||
MARKER_DIRNAME = ".starpilot_param_migrations"
|
||||
|
||||
LATERAL_METHOD_PARAM_SUFFIXES = (
|
||||
"ActiveOverrides",
|
||||
"ActiveProfileId",
|
||||
"TrialBaseline",
|
||||
"TrialApplied",
|
||||
)
|
||||
|
||||
LEGACY_CE_STOPPED_LEAD_DEFAULT = True
|
||||
LEGACY_FORCE_STOPS_DEFAULT = False
|
||||
LEGACY_AGGRESSIVE_FOLLOW_HIGH_DEFAULT = 1.25
|
||||
@@ -88,6 +96,10 @@ def _use_old_ui_marker_path(params: ParamsLike) -> Path:
|
||||
return _marker_dir_path(params) / USE_OLD_UI_MIGRATION_MARKER
|
||||
|
||||
|
||||
def _lateral_method_rebrand_marker_path(params: ParamsLike) -> Path:
|
||||
return _marker_dir_path(params) / LATERAL_METHOD_REBRAND_MIGRATION_MARKER
|
||||
|
||||
|
||||
def _marker_dir_path(params: ParamsLike) -> Path:
|
||||
params_path = Path(params.get_param_path())
|
||||
# Params.clear_all() removes unknown files inside the params directory, so
|
||||
@@ -180,10 +192,29 @@ def _apply_use_old_ui_migration(params: ParamsLike, marker: Path) -> None:
|
||||
marker.touch()
|
||||
|
||||
|
||||
def _apply_lateral_method_rebrand_migration(params: ParamsLike, marker: Path) -> None:
|
||||
if marker.exists():
|
||||
return
|
||||
|
||||
marker.parent.mkdir(parents=True, exist_ok=True)
|
||||
legacy_prefix = "".join(("F", "T", "M"))
|
||||
|
||||
for suffix in LATERAL_METHOD_PARAM_SUFFIXES:
|
||||
legacy_path = Path(params.get_param_path(f"{legacy_prefix}{suffix}"))
|
||||
current_path = Path(params.get_param_path(f"FLM{suffix}"))
|
||||
if legacy_path.is_file():
|
||||
current_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
current_path.write_bytes(legacy_path.read_bytes())
|
||||
legacy_path.unlink()
|
||||
|
||||
marker.touch()
|
||||
|
||||
|
||||
def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None = None,
|
||||
branch_defaults_marker_path: Path | None = None,
|
||||
acceleration_profile_marker_path: Path | None = None,
|
||||
use_old_ui_marker_path: Path | None = None) -> None:
|
||||
use_old_ui_marker_path: Path | None = None,
|
||||
lateral_method_rebrand_marker_path: Path | None = None) -> None:
|
||||
_apply_legacy_launch_param_migrations(params, marker_path or _default_marker_path(params))
|
||||
# Keep branch-default rollout on its own marker so older installs that already
|
||||
# have the legacy marker still receive this one-time param reset.
|
||||
@@ -192,6 +223,9 @@ def apply_launch_param_migrations(params: ParamsLike, marker_path: Path | None =
|
||||
params, acceleration_profile_marker_path or _acceleration_profile_marker_path(params)
|
||||
)
|
||||
_apply_use_old_ui_migration(params, use_old_ui_marker_path or _use_old_ui_marker_path(params))
|
||||
_apply_lateral_method_rebrand_migration(
|
||||
params, lateral_method_rebrand_marker_path or _lateral_method_rebrand_marker_path(params)
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
||||
@@ -5,6 +5,7 @@ from openpilot.system.manager.launch_param_migrations import (
|
||||
BRANCH_DEFAULTS_MIGRATION_MARKER,
|
||||
DEFAULT_STEER_KP,
|
||||
LAUNCH_PARAM_MIGRATION_MARKER,
|
||||
LATERAL_METHOD_REBRAND_MIGRATION_MARKER,
|
||||
MARKER_DIRNAME,
|
||||
STANDARD_ACCELERATION_PROFILE,
|
||||
USE_OLD_UI_MIGRATION_MARKER,
|
||||
@@ -49,6 +50,9 @@ class FileBackedFakeParams:
|
||||
def put_float(self, key, value):
|
||||
Path(self.get_param_path(key)).write_text(str(float(value)), encoding="utf-8")
|
||||
|
||||
def put(self, key, value):
|
||||
Path(self.get_param_path(key)).write_text(str(value), encoding="utf-8")
|
||||
|
||||
|
||||
def marker_path(tmp_path: Path, marker_name: str) -> Path:
|
||||
path = tmp_path / MARKER_DIRNAME / "params" / marker_name
|
||||
@@ -235,3 +239,23 @@ def test_apply_launch_param_migrations_does_not_overwrite_use_old_ui(tmp_path):
|
||||
apply_launch_param_migrations(params)
|
||||
|
||||
assert not params.get_bool("UseOldUI")
|
||||
|
||||
|
||||
def test_apply_launch_param_migrations_preserves_active_lateral_method_trial(tmp_path):
|
||||
params = FileBackedFakeParams(tmp_path / "params")
|
||||
legacy_prefix = "".join(("F", "T", "M"))
|
||||
legacy_values = {
|
||||
"ActiveOverrides": '{"vehicleKnobs":{"generic.ff_gain_left":0.12}}',
|
||||
"ActiveProfileId": "report:cleanup:recommended",
|
||||
"TrialBaseline": '{"params":{"SteerLatAccel":1.8}}',
|
||||
"TrialApplied": "1",
|
||||
}
|
||||
for suffix, value in legacy_values.items():
|
||||
params.put(f"{legacy_prefix}{suffix}", value)
|
||||
|
||||
apply_launch_param_migrations(params)
|
||||
|
||||
for suffix, value in legacy_values.items():
|
||||
assert params.get(f"FLM{suffix}") == value
|
||||
assert not Path(params.get_param_path(f"{legacy_prefix}{suffix}")).exists()
|
||||
assert marker_path(tmp_path, LATERAL_METHOD_REBRAND_MIGRATION_MARKER).is_file()
|
||||
|
||||
@@ -32,7 +32,7 @@ KNOWN_READ_ONLY = {
|
||||
"ClusterOffset", "Compass", "DeveloperSidebarMetric1", "DeveloperSidebarMetric2",
|
||||
"DeveloperSidebarMetric3", "DeveloperSidebarMetric4", "DeveloperSidebarMetric5",
|
||||
"DeveloperSidebarMetric6", "DeveloperSidebarMetric7", "DongleId",
|
||||
"FTMActiveOverrides", "FTMActiveProfileId", "FTMTrialApplied", "FTMTrialBaseline",
|
||||
"FLMActiveOverrides", "FLMActiveProfileId", "FLMTrialApplied", "FLMTrialBaseline",
|
||||
"StarPilotCarParamsPersistent", "StarPilotDrives", "StarPilotKilometers",
|
||||
"StarPilotMinutes", "GitBranch", "GitCommit", "GitCommitDate", "GitDiff",
|
||||
"GitRemote", "GithubSshKeys", "GithubUsername", "HardwareSerial", "IMEI",
|
||||
|
||||
@@ -3,11 +3,11 @@ import argparse
|
||||
import json
|
||||
|
||||
from openpilot.system.hardware.hw import Paths
|
||||
from openpilot.starpilot.system.the_galaxy import ftm_workspace
|
||||
from openpilot.starpilot.system.the_galaxy import flm_workspace
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Run the Firestar Tuning Method analyzer against local routes.")
|
||||
parser = argparse.ArgumentParser(description="Run the Firestar Lateral Method analyzer against local routes.")
|
||||
parser.add_argument("routes", nargs="+", help="One or more local route ids.")
|
||||
parser.add_argument("--report-id", dest="report_id", default=None, help="Optional report id override.")
|
||||
args = parser.parse_args()
|
||||
@@ -17,7 +17,7 @@ def main() -> None:
|
||||
str(Paths.log_root(konik=True, raw=True)),
|
||||
str(Paths.log_root(raw=True)),
|
||||
]
|
||||
report = ftm_workspace.analyze_routes(args.routes, footage_paths, report_id=args.report_id)
|
||||
report = flm_workspace.analyze_routes(args.routes, footage_paths, report_id=args.report_id)
|
||||
print(json.dumps({
|
||||
"reportId": report["reportId"],
|
||||
"htmlPath": report["htmlPath"],
|
||||
Reference in New Issue
Block a user