Japanese BBQ Sauce

This commit is contained in:
firestar5683
2026-07-20 11:58:21 -05:00
parent 11044975c5
commit f265aa3f7b
14 changed files with 270 additions and 29 deletions
@@ -15,8 +15,15 @@ import cv2
from starpilot.system.speed_limit_vision import DETECTOR_CLASSIFIER_EXPANSIONS
if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import SUPPORTED_SPEED_VALUES # type: ignore # noqa: TID251
else:
from .common import SUPPORTED_SPEED_VALUES
SPEED_VALUES = frozenset((15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75))
SPEED_VALUES = frozenset(SUPPORTED_SPEED_VALUES)
def parse_args() -> argparse.Namespace:
+5
View File
@@ -23,6 +23,11 @@ DETECTOR_CLASS_NAMES = (
"school_zone_speed_limit",
)
DEFAULT_SPEED_VALUES = (15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75)
# Values the review and dataset tooling can accept. Keep DEFAULT_SPEED_VALUES
# aligned with the currently deployed classifier until an expanded model is
# promoted; adding a class changes every output index after it.
SUPPORTED_SPEED_VALUES = (10, *DEFAULT_SPEED_VALUES, 80, 90, 100)
EXTENDED_CLASSIFIER_SPEED_VALUES = tuple(sorted(SUPPORTED_SPEED_VALUES, key=str))
DETECTOR_EXPORT_NAME = "speed_limit_us_detector.onnx"
CLASSIFIER_EXPORT_NAME = "speed_limit_us_value_classifier.onnx"
@@ -2,7 +2,6 @@
from __future__ import annotations
import argparse
import math
import random
from dataclasses import dataclass
@@ -16,7 +15,7 @@ from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont
if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore # noqa: TID251
else:
from .common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace
@@ -100,7 +99,6 @@ def render_regulatory_sign(speed_value: int, school_zone: bool, seed: int) -> Im
def render_advisory_sign(speed_value: int, seed: int) -> Image.Image:
rng = random.Random(seed)
size = rng.randint(240, 320)
image = Image.new("RGBA", (size, size), (255, 255, 255, 0))
base = Image.new("RGBA", (size, size), (255, 255, 255, 0))
draw = ImageDraw.Draw(base)
@@ -234,6 +232,7 @@ def main():
parser.add_argument("--train-count", type=int, default=9000, help="Number of synthetic training detector images.")
parser.add_argument("--val-count", type=int, default=1200, help="Number of synthetic validation detector images.")
parser.add_argument("--negative-ratio", type=float, default=0.18, help="Share of detector images with no sign.")
parser.add_argument("--speed-values", nargs="+", type=int, default=list(DEFAULT_SPEED_VALUES), help="Posted values to synthesize.")
parser.add_argument("--seed", type=int, default=20260330, help="Random seed.")
args = parser.parse_args()
@@ -246,7 +245,7 @@ def main():
detector_image_dir = workspace / "detector" / "images"
detector_label_dir = workspace / "detector" / "labels"
classifier_dir = workspace / "classifier"
speed_values = tuple(DEFAULT_SPEED_VALUES)
speed_values = tuple(dict.fromkeys(args.speed_values))
rng = random.Random(args.seed)
for split, count in (("train", max(args.train_count, 0)), ("val", max(args.val_count, 0))):
@@ -13,7 +13,7 @@ import numpy as np
if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore # noqa: TID251
from generate_synthetic_us_speed_limits import KNOWN_REAL_CROPS, augment_sign, render_regulatory_sign # type: ignore
else:
from .common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace
@@ -146,6 +146,7 @@ def main():
parser.add_argument("--train-per-class", type=int, default=1800, help="Synthetic training samples per value.")
parser.add_argument("--val-per-class", type=int, default=260, help="Synthetic validation samples per value.")
parser.add_argument("--real-augmentations", type=int, default=28, help="Augmented mask samples to create per known real crop.")
parser.add_argument("--speed-values", nargs="+", type=int, default=list(DEFAULT_SPEED_VALUES), help="Posted values to synthesize.")
parser.add_argument("--seed", type=int, default=20260330, help="Random seed.")
args = parser.parse_args()
@@ -157,7 +158,7 @@ def main():
ensure_dir(classifier_dir / "val")
rng = random.Random(args.seed)
speed_values = tuple(DEFAULT_SPEED_VALUES)
speed_values = tuple(dict.fromkeys(args.speed_values))
for split, per_class in (("train", max(args.train_per_class, 0)), ("val", max(args.val_per_class, 0))):
for speed_value in speed_values:
@@ -15,11 +15,11 @@ if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from build_value_dataset import crop_box, parse_yolo_labels # type: ignore
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore
from common import DEFAULT_WORKSPACE, SUPPORTED_SPEED_VALUES, ensure_dir, resolve_workspace # type: ignore # noqa: TID251
from generate_value_roi_classifier_dataset import augment_mask, extract_value_mask # type: ignore
else:
from .build_value_dataset import crop_box, parse_yolo_labels
from .common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace
from .common import DEFAULT_WORKSPACE, SUPPORTED_SPEED_VALUES, ensure_dir, resolve_workspace
from .generate_value_roi_classifier_dataset import augment_mask, extract_value_mask
@@ -60,7 +60,7 @@ def parse_speed_from_read(text: str) -> int:
if not match:
return 0
value = int(match.group(1))
return value if value in DEFAULT_SPEED_VALUES else 0
return value if value in SUPPORTED_SPEED_VALUES else 0
def row_speed(row: dict[str, str]) -> int:
@@ -68,7 +68,7 @@ def row_speed(row: dict[str, str]) -> int:
text = (row.get(field) or "").strip()
if text.isdigit():
value = int(text)
if value in DEFAULT_SPEED_VALUES:
if value in SUPPORTED_SPEED_VALUES:
return value
for field in ("full_detection", "model_read", "ocr_read"):
value = parse_speed_from_read(row.get(field, ""))
@@ -235,12 +235,12 @@ def main() -> int:
if args.max_rows > 0 and attempted >= args.max_rows:
break
print(
"Imported manifest classifier masks: "
f"attempted={attempted} imported={imported} written={written} "
f"skipped_no_speed={skipped_no_speed} skipped_no_crop={skipped_no_crop} skipped_no_mask={skipped_no_mask} "
f"skipped_write_failed={skipped_write_failed}"
)
print("".join((
"Imported manifest classifier masks: ",
f"attempted={attempted} imported={imported} written={written} ",
f"skipped_no_speed={skipped_no_speed} skipped_no_crop={skipped_no_crop} skipped_no_mask={skipped_no_mask} ",
f"skipped_write_failed={skipped_write_failed}",
)))
return 0
@@ -14,9 +14,9 @@ import cv2
if __package__ in (None, ""):
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace # type: ignore # noqa: TID251
from common import DEFAULT_WORKSPACE, SUPPORTED_SPEED_VALUES, ensure_dir, resolve_workspace # type: ignore # noqa: TID251
else:
from .common import DEFAULT_SPEED_VALUES, DEFAULT_WORKSPACE, ensure_dir, resolve_workspace
from .common import DEFAULT_WORKSPACE, SUPPORTED_SPEED_VALUES, ensure_dir, resolve_workspace
CLASSIFIER_FIELDNAMES = [
@@ -149,7 +149,7 @@ def parse_speed(text: str) -> int:
value = int(float(text))
except ValueError:
return 0
return value if value in DEFAULT_SPEED_VALUES else 0
return value if value in SUPPORTED_SPEED_VALUES else 0
def parse_bbox(text: str) -> tuple[int, int, int, int] | None:
@@ -37,6 +37,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("routes", nargs="+", help="Route ids like 'dongle/logid'.")
parser.add_argument("--workspace", type=Path, default=DEFAULT_WORKSPACE, help="Training workspace root.")
parser.add_argument("--clip-root", type=Path, default=preferred_clip_root(), help="Downloaded route clip root.")
parser.add_argument("--output-dir", type=Path, help="Output directory. Defaults to <workspace>/review/connect_route_bookmarks.")
parser.add_argument("--models-dir", type=Path, help="Optional directory containing speed_limit_us_detector.onnx and speed_limit_us_value_classifier.onnx.")
parser.add_argument("--lead-in", type=float, default=7.0, help="Seconds before each bookmark to sample into review sheets.")
parser.add_argument("--sample-every", type=float, default=0.5, help="Seconds between sampled lead-in frames.")
@@ -154,7 +155,7 @@ def main() -> int:
args = parse_args()
workspace = resolve_workspace(args.workspace)
clip_root = args.clip_root.expanduser().resolve()
review_root = ensure_dir(workspace / "review" / "connect_route_bookmarks")
review_root = ensure_dir(args.output_dir.expanduser().resolve()) if args.output_dir else ensure_dir(workspace / "review" / "connect_route_bookmarks")
frame_dir = ensure_dir(review_root / "frames")
crop_dir = ensure_dir(review_root / "crops")
contact_sheet_dir = ensure_dir(review_root / "contact_sheets")
@@ -9,7 +9,7 @@ from collections import Counter, defaultdict, deque
from pathlib import Path
PRIORITY_SPEED_ORDER = (60, 65, 55, 50, 45, 40, 35, 30, 25, 20, 70, 15, 75)
PRIORITY_SPEED_ORDER = (60, 65, 55, 50, 45, 40, 35, 30, 25, 20, 70, 75, 80, 90, 100, 15, 10)
COMPARISON_PRIORITY_BONUS = {
"value_changed": 4.0,
"gained_read": 3.0,
@@ -130,7 +130,7 @@ HTML = r"""<!doctype html>
</aside>
</main>
<script>
const speeds = [15,20,25,30,35,40,45,50,55,60,65,70,75];
const speeds = [10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,90,100];
let rows = [];
let index = 0;
let current = null;
@@ -33,6 +33,16 @@ def test_raw_comma_camera_uses_real_frame_rate():
assert common.source_video_fps(Path("clip.mp4"), 0.0) == 20.0
def test_extended_classifier_order_matches_lexical_dataset_classes():
assert common.SUPPORTED_SPEED_VALUES == (10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 90, 100)
assert common.EXTENDED_CLASSIFIER_SPEED_VALUES == (10, 100, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 90)
@pytest.mark.parametrize("speed", (10, 80, 90, 100))
def test_manual_import_accepts_extended_speed_values(speed):
assert import_queue.parse_speed(str(speed)) == speed
def test_localized_bookmark_source_position_normalizes_previous_segment():
previous = {"segment": "26", "relative_time_s": "-18.950"}
current = {"segment": "26", "relative_time_s": "12.500"}
+20
View File
@@ -346,6 +346,25 @@ class LongControl:
target_factor *= 0.75
self.pid.i *= target_factor
def _trim_gm_truck_negative_hold_integrator(self, a_target, error, CS):
if not self.is_gm_stock_truck or self.pid.i >= -0.02:
return
if CS.vEgo < 12.0 or a_target <= -0.85:
return
if error <= 0.04:
return
authority_mismatch = float(a_target) - float(self.last_output_accel)
if authority_mismatch <= 0.10:
return
release = float(interp(
max(authority_mismatch, error),
[0.10, 0.25, 0.50],
[0.0008, 0.0020, 0.0040],
))
self.pid.i = min(0.0, self.pid.i + release)
def _apply_pedal_long_brake_bias(self, output_accel, a_target, CS):
if not self.is_gm_pedal_long:
return output_accel
@@ -431,6 +450,7 @@ class LongControl:
self._shape_volt_test_tune_integrator(error, CS.vEgo)
self._trim_positive_overshoot_integrator(a_target, error, CS)
self._trim_gm_truck_positive_hold_integrator(a_target, error, CS)
self._trim_gm_truck_negative_hold_integrator(a_target, error, CS)
feedforward = self._get_longitudinal_feedforward(a_target, CS.vEgo)
freeze_integrator = self._get_pedal_long_freeze(a_target, error, CS.vEgo, accel_limits)
raw_output_accel = self.pid.update(error, speed=CS.vEgo, feedforward=feedforward,
@@ -726,3 +726,92 @@ def test_gm_stock_truck_positive_i_trim_preserves_low_speed_launch():
lc._trim_gm_truck_positive_hold_integrator(0.05, 0.05, CS)
assert lc.pid.i == pytest.approx(0.25, abs=1e-9)
def test_gm_stock_truck_negative_i_unwinds_when_already_overbraking():
CP = make_longcontrol_cp(
brand="gm",
carFingerprint=CAR.CHEVROLET_SILVERADO,
enableGasInterceptorDEPRECATED=False,
)
lc = LongControl(CP)
lc.pid.i = -0.22
lc.last_output_accel = -0.66
CS = car.CarState.new_message(vEgo=29.6, aEgo=-0.49, brakePressed=False)
lc._trim_gm_truck_negative_hold_integrator(-0.44, 0.05, CS)
assert -0.22 < lc.pid.i < 0.0
def test_gm_stock_truck_negative_i_stays_when_decel_is_not_achieved():
CP = make_longcontrol_cp(
brand="gm",
carFingerprint=CAR.CHEVROLET_SILVERADO,
enableGasInterceptorDEPRECATED=False,
)
lc = LongControl(CP)
lc.pid.i = -0.22
lc.last_output_accel = -0.66
CS = car.CarState.new_message(vEgo=29.6, aEgo=0.05, brakePressed=False)
lc._trim_gm_truck_negative_hold_integrator(-0.44, -0.49, CS)
assert lc.pid.i == pytest.approx(-0.22, abs=1e-9)
def test_gm_stock_truck_negative_i_stays_for_urgent_braking():
CP = make_longcontrol_cp(
brand="gm",
carFingerprint=CAR.CHEVROLET_SILVERADO,
enableGasInterceptorDEPRECATED=False,
)
lc = LongControl(CP)
lc.pid.i = -0.22
lc.last_output_accel = -1.30
CS = car.CarState.new_message(vEgo=29.6, aEgo=-1.20, brakePressed=False)
lc._trim_gm_truck_negative_hold_integrator(-1.00, 0.20, CS)
assert lc.pid.i == pytest.approx(-0.22, abs=1e-9)
def test_gm_stock_truck_negative_i_trim_does_not_affect_other_gm_cars():
CP = make_longcontrol_cp(
brand="gm",
carFingerprint=CAR.CHEVROLET_BOLT_ACC_2022_2023,
enableGasInterceptorDEPRECATED=False,
)
lc = LongControl(CP)
lc.pid.i = -0.22
lc.last_output_accel = -0.66
CS = car.CarState.new_message(vEgo=29.6, aEgo=-0.49, brakePressed=False)
lc._trim_gm_truck_negative_hold_integrator(-0.44, 0.05, CS)
assert lc.pid.i == pytest.approx(-0.22, abs=1e-9)
def test_gm_stock_truck_update_gradually_releases_stale_brake_integral():
CP = make_longcontrol_cp(
brand="gm",
carFingerprint=CAR.CHEVROLET_SILVERADO,
enableGasInterceptorDEPRECATED=False,
)
lc = LongControl(CP)
lc.pid.i = -0.22
lc.last_output_accel = -0.66
CS = car.CarState.new_message(vEgo=29.6, aEgo=-0.49, brakePressed=False)
CS.cruiseState.standstill = False
output_accel = lc.update(
active=True,
CS=CS,
a_target=-0.44,
should_stop=False,
accel_limits=(-3.5, 2.0),
starpilot_toggles=make_toggles(),
has_lead=True,
)
assert -0.66 < output_accel < -0.44
+35 -1
View File
@@ -18,6 +18,9 @@ from openpilot.starpilot.common.favorite_slots import toggle_favorite_slot
from openpilot.starpilot.common.starpilot_utilities import is_FrogsGoMoo
from openpilot.starpilot.common.starpilot_variables import ERROR_LOGS_PATH, GearShifter, NON_DRIVING_GEARS
HYUNDAI_MAIN_CRUISE_AOL_CONFIRM_TIMEOUT_FRAMES = 100
class StarPilotCard:
@staticmethod
def _button_type_raw(button_event) -> int:
@@ -40,6 +43,9 @@ class StarPilotCard:
self.hyundai_preserve_aol_across_reverse = getattr(self.CP, "carFingerprint", None) == HYUNDAI_CAR.HYUNDAI_SONATA_HYBRID
self.hyundai_aol_needs_engagement = self.CP.brand == "hyundai" and not (hyundai_flags & HyundaiFlags.CANFD) and not kia_forte_non_scc
self.hyundai_aol_ready = False
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
self.prev_cruise_available = None
self.prev_active = False
self.prev_cruise_enabled = False
self.decel_pressed = False
@@ -116,6 +122,11 @@ class StarPilotCard:
button_event_types = [self._button_type_raw(be) for be in carState.buttonEvents]
button_aol_supported = self.CP.brand == "hyundai" or starpilot_toggles.lkas_allowed_for_aol
button_managed_aol = starpilot_toggles.always_on_lateral_lkas or (button_aol_supported and starpilot_toggles.main_cruise_aol_toggle)
hyundai_main_cruise_aol_managed = self.CP.brand == "hyundai" and starpilot_toggles.main_cruise_aol_toggle
if carState.gearShifter in NON_DRIVING_GEARS or not hyundai_main_cruise_aol_managed:
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
if self.hyundai_aol_needs_engagement:
if carState.gearShifter in NON_DRIVING_GEARS:
@@ -129,6 +140,8 @@ class StarPilotCard:
if button_aol_supported:
for be, be_type in zip(carState.buttonEvents, button_event_types, strict=False):
if be_type == ButtonType.lkas and be.pressed and starpilot_toggles.always_on_lateral_lkas:
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
if self.hyundai_aol_needs_engagement:
self.hyundai_aol_ready = True
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
@@ -138,9 +151,27 @@ class StarPilotCard:
if starpilot_toggles.main_cruise_aol_toggle:
if self.hyundai_aol_needs_engagement:
self.hyundai_aol_ready = True
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
if hyundai_main_cruise_aol_managed:
# Panda permits Hyundai main-button AOL only after the vehicle reports main on.
self.main_cruise_aol_pending = True
self.main_cruise_aol_pending_frames = 0
else:
self.always_on_lateral_allowed = not self.always_on_lateral_allowed
elif starpilot_toggles.main_cruise_slc_adopt and starpilot_toggles.speed_limit_controller:
self.params_memory.put_bool("SLCAdoptSpeedLimit", True)
cruise_available_changed = self.prev_cruise_available is not None and carState.cruiseState.available != self.prev_cruise_available
if self.main_cruise_aol_pending:
if cruise_available_changed:
self.always_on_lateral_allowed = carState.cruiseState.available
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
else:
self.main_cruise_aol_pending_frames += 1
if self.main_cruise_aol_pending_frames >= HYUNDAI_MAIN_CRUISE_AOL_CONFIRM_TIMEOUT_FRAMES:
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
if starpilot_toggles.always_on_lateral_main and not button_managed_aol:
car_fingerprint = getattr(self.CP, "carFingerprint", None)
pcm_cruise = getattr(self.CP, "pcmCruise", False)
@@ -157,11 +188,14 @@ class StarPilotCard:
# On rising edge of engagement (SET press enabling lat+long), auto-enable AOL
# so that lateral persists when braking disengages longitudinal
if sm["selfdriveState"].active and not self.prev_active and self.always_on_lateral_set and starpilot_toggles.always_on_lateral_lkas:
self.main_cruise_aol_pending = False
self.main_cruise_aol_pending_frames = 0
if self.hyundai_aol_needs_engagement:
self.hyundai_aol_ready = True
self.always_on_lateral_allowed = True
self.prev_active = sm["selfdriveState"].active
self.prev_cruise_available = carState.cruiseState.available
self.prev_cruise_enabled = carState.cruiseState.enabled
self.always_on_lateral_enabled = self.always_on_lateral_allowed and self.always_on_lateral_set
@@ -278,7 +278,7 @@ def test_hyundai_canfd_lkas_button_wrapped_enum_can_toggle_aol(monkeypatch, tmp_
assert ret.alwaysOnLateralEnabled is False
def test_kia_forte_non_scc_main_cruise_button_can_toggle_aol_before_engagement(monkeypatch, tmp_path):
def test_kia_forte_non_scc_main_cruise_button_waits_for_main_confirmation(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
@@ -292,18 +292,24 @@ def test_kia_forte_non_scc_main_cruise_button_can_toggle_aol_before_engagement(m
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
car_state = make_car_state(available=True, button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True)
card.update(make_car_state(), starpilot_car_state, sm, toggles)
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
assert ret.alwaysOnLateralEnabled is False
car_state.buttonEvents = []
car_state.cruiseState.available = True
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
def test_hyundai_main_cruise_button_can_start_aol_before_normal_engagement(monkeypatch, tmp_path):
def test_hyundai_main_cruise_button_follows_confirmed_main_state(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
@@ -313,17 +319,29 @@ def test_hyundai_main_cruise_button_can_start_aol_before_normal_engagement(monke
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
car_state = make_car_state(available=True, button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True)
card.update(make_car_state(), starpilot_car_state, sm, toggles)
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
assert ret.alwaysOnLateralEnabled is False
car_state.buttonEvents = []
car_state.cruiseState.available = True
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
car_state.buttonEvents = [SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)]
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
car_state.buttonEvents = []
car_state.cruiseState.available = False
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
@@ -337,21 +355,78 @@ def test_hyundai_main_cruise_button_wrapped_enum_can_toggle_aol(monkeypatch, tmp
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
car_state = make_car_state(available=True, button_events=[make_wrapped_button_event(spc.ButtonType.mainCruise, True)])
car_state = make_car_state()
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True)
card.update(car_state, starpilot_car_state, sm, toggles)
car_state.buttonEvents = [make_wrapped_button_event(spc.ButtonType.mainCruise, True)]
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
assert ret.alwaysOnLateralEnabled is False
car_state.buttonEvents = []
car_state.cruiseState.available = True
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
car_state.buttonEvents = [make_wrapped_button_event(spc.ButtonType.mainCruise, True)]
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
car_state.buttonEvents = []
car_state.cruiseState.available = False
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
assert ret.alwaysOnLateralEnabled is False
def test_hyundai_main_cruise_aol_ignores_unconfirmed_button_press(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="hyundai"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
starpilot_car_state = SimpleNamespace(distancePressed=False)
sm = make_sm()
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True)
ret = card.update(car_state, starpilot_car_state, sm, toggles)
car_state.buttonEvents = []
for _ in range(spc.HYUNDAI_MAIN_CRUISE_AOL_CONFIRM_TIMEOUT_FRAMES):
ret = card.update(car_state, starpilot_car_state, sm, toggles)
assert ret.alwaysOnLateralAllowed is False
assert ret.alwaysOnLateralEnabled is False
assert card.main_cruise_aol_pending is False
def test_honda_mapped_main_cruise_button_keeps_immediate_toggle(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
monkeypatch.setattr(spc, "ERROR_LOGS_PATH", tmp_path)
card = spc.StarPilotCard(
SimpleNamespace(brand="honda"),
SimpleNamespace(alternativeExperience=spc.ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL),
)
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.mainCruise, pressed=True)])
toggles = make_toggles(always_on_lateral=True, main_cruise_aol_toggle=True, lkas_allowed_for_aol=True)
ret = card.update(car_state, SimpleNamespace(distancePressed=False), make_sm(), toggles)
assert ret.alwaysOnLateralAllowed is True
assert ret.alwaysOnLateralEnabled is True
assert card.main_cruise_aol_pending is False
def test_hyundai_main_cruise_button_adopts_slc_when_assigned_to_slc(monkeypatch, tmp_path):
monkeypatch.setattr(spc, "Params", FakeParams)
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)