mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-10 10:13:45 +08:00
cilantro lime
This commit is contained in:
Binary file not shown.
@@ -466,6 +466,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LeadDetectionThreshold", {PERSISTENT, INT, "35", "50", 3}},
|
||||
{"LeadIndicator", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LeadInfo", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"LeadInfoMode", {PERSISTENT, INT, "2", "2", 3}},
|
||||
{"LKASButtonControl", {PERSISTENT, INT, "5", "0", 2, SETTINGS_SIMPLE}},
|
||||
{"LockDoors", {PERSISTENT, BOOL, "1", "0", 0}},
|
||||
{"LockDoorsTimer", {PERSISTENT, INT, "0", "0", 0}},
|
||||
|
||||
Binary file not shown.
@@ -1497,7 +1497,7 @@ BO_ 913 BCM_PO_11: 8 Vector__XXX
|
||||
SG_ BCM_Door_Dri_Status : 5|1@0+ (1,0) [0|1] "" PT_ESC_ABS
|
||||
SG_ BCM_Shift_R_MT_SW_Status : 39|2@0+ (1,0) [0|3] "" PT_ESC_ABS
|
||||
SG_ LDA_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 0|2@1+ (1,0) [0|3] "" XXX
|
||||
SG_ RAY_LKAS_BTN : 4|1@0+ (1,0) [0|1] "" XXX
|
||||
|
||||
BO_ 1426 LABEL11: 8 XXX
|
||||
SG_ CC_React : 34|1@1+ (1,0) [0|1] "" XXX
|
||||
|
||||
@@ -19,6 +19,7 @@ import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@@ -619,6 +620,70 @@ def update_manifest(repo: Path, info: ReleaseInfo, result: dict, manifest_versio
|
||||
return path
|
||||
|
||||
|
||||
def manifest_models(payload: object) -> list[dict]:
|
||||
models = payload.get("models") if isinstance(payload, dict) else payload
|
||||
if not isinstance(models, list) or not models:
|
||||
raise ReleaseError("Unsupported or empty Hugging Face manifest")
|
||||
if any(not isinstance(model, dict) or not str(model.get("id") or "").strip() for model in models):
|
||||
raise ReleaseError("Hugging Face manifest contains an invalid model entry")
|
||||
model_ids = [str(model["id"]).strip() for model in models]
|
||||
if len(model_ids) != len(set(model_ids)):
|
||||
raise ReleaseError("Hugging Face manifest contains duplicate model IDs")
|
||||
return models
|
||||
|
||||
|
||||
def accelerator_artifact_map(payload: object) -> dict[tuple[str, str], dict]:
|
||||
artifacts: dict[tuple[str, str], dict] = {}
|
||||
for model in manifest_models(payload):
|
||||
model_id = str(model.get("id") or "").strip()
|
||||
model_artifacts = model.get("accelerator_artifacts")
|
||||
if not model_id or not isinstance(model_artifacts, dict):
|
||||
continue
|
||||
for accelerator, metadata in model_artifacts.items():
|
||||
if isinstance(metadata, dict):
|
||||
artifacts[(model_id, str(accelerator))] = metadata
|
||||
return artifacts
|
||||
|
||||
|
||||
def validate_manifest_update(before: object, after: object, replacing_model_id: str) -> None:
|
||||
before_by_id = {str(model["id"]).strip(): model for model in manifest_models(before)}
|
||||
after_by_id = {str(model["id"]).strip(): model for model in manifest_models(after)}
|
||||
before_ids = set(before_by_id)
|
||||
after_ids = set(after_by_id)
|
||||
expected_ids = before_ids | {replacing_model_id}
|
||||
if after_ids != expected_ids:
|
||||
missing = sorted(expected_ids - after_ids)
|
||||
unexpected = sorted(after_ids - expected_ids)
|
||||
raise ReleaseError(
|
||||
"Refusing to publish a manifest with an unexpected model set"
|
||||
+ (f"; missing: {', '.join(missing)}" if missing else "")
|
||||
+ (f"; unexpected: {', '.join(unexpected)}" if unexpected else "")
|
||||
)
|
||||
|
||||
before_artifacts = accelerator_artifact_map(before)
|
||||
after_artifacts = accelerator_artifact_map(after)
|
||||
regressions = [
|
||||
f"{model_id}:{accelerator}"
|
||||
for (model_id, accelerator), metadata in before_artifacts.items()
|
||||
if model_id != replacing_model_id and after_artifacts.get((model_id, accelerator)) != metadata
|
||||
]
|
||||
if regressions:
|
||||
raise ReleaseError(
|
||||
"Refusing to publish a manifest that removes or changes existing accelerator metadata for: "
|
||||
+ ", ".join(sorted(regressions))
|
||||
)
|
||||
|
||||
unrelated_changes = sorted(
|
||||
model_id for model_id, model in before_by_id.items()
|
||||
if model_id != replacing_model_id and after_by_id[model_id] != model
|
||||
)
|
||||
if unrelated_changes:
|
||||
raise ReleaseError(
|
||||
"Refusing to publish a manifest that changes unrelated model entries: "
|
||||
+ ", ".join(unrelated_changes)
|
||||
)
|
||||
|
||||
|
||||
def find_hf() -> str:
|
||||
candidates = [shutil.which("hf"), str(Path.home() / ".local/bin/hf")]
|
||||
for candidate in candidates:
|
||||
@@ -633,15 +698,42 @@ def hf_copy(source: Path, bucket: str, remote_path: str) -> None:
|
||||
run([hf, "buckets", "cp", str(source), destination, "--format", "quiet"])
|
||||
|
||||
|
||||
def refresh_huggingface_manifest(manifest: Path, bucket: str) -> dict:
|
||||
remote_path = f"manifests/{manifest.name}"
|
||||
source = f"hf://buckets/{bucket}/{remote_path}"
|
||||
manifest.parent.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix=".model-release-", dir=manifest.parent) as temporary_dir:
|
||||
candidate = Path(temporary_dir) / manifest.name
|
||||
run([find_hf(), "buckets", "cp", source, str(candidate), "--format", "quiet"])
|
||||
try:
|
||||
payload = json.loads(candidate.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ReleaseError(f"Invalid live Hugging Face manifest: {error}") from error
|
||||
accelerator_artifact_map(payload)
|
||||
candidate.replace(manifest)
|
||||
return payload
|
||||
|
||||
|
||||
def prepare_huggingface_manifest(manifest: Path, info: ReleaseInfo, result: dict,
|
||||
bucket: str, manifest_version: str) -> Path:
|
||||
live_payload = refresh_huggingface_manifest(manifest, bucket)
|
||||
updated_manifest = update_manifest(manifest.parent, info, result, manifest_version)
|
||||
updated_payload = json.loads(updated_manifest.read_text(encoding="utf-8"))
|
||||
validate_manifest_update(live_payload, updated_payload, info.model_id)
|
||||
return updated_manifest
|
||||
|
||||
|
||||
def upload_huggingface(info: ReleaseInfo, result: dict, workspace: Path, bucket: str, manifest_version: str,
|
||||
manifest: Path, upload_onnx: bool, source: Path) -> None:
|
||||
manifest: Path, upload_onnx: bool, source: Path) -> Path:
|
||||
artifact_dir = Path(result["path"])
|
||||
for filename in result["files"]:
|
||||
hf_copy(artifact_dir / filename, bucket, f"models/{manifest_version}/{info.model_id}/{filename}")
|
||||
if upload_onnx:
|
||||
hf_copy(source, bucket, f"onnx/{info.model_id}/{source.name}")
|
||||
manifest = prepare_huggingface_manifest(manifest, info, result, bucket, manifest_version)
|
||||
hf_copy(manifest, bucket, f"manifests/{manifest.name}")
|
||||
print(f"Hugging Face upload complete: {bucket}/models/{manifest_version}/{info.model_id}/")
|
||||
return manifest
|
||||
|
||||
|
||||
def git_output(repo: Path, args: list[str]) -> str:
|
||||
@@ -777,9 +869,9 @@ def main() -> int:
|
||||
result = remote_compile(info, source, ip, workspace, args.keep_device_files)
|
||||
resources_repo = args.resources_repo.expanduser().resolve()
|
||||
check_resources_repo(resources_repo, args.resources_branch)
|
||||
manifest = update_manifest(resources_repo, info, result, args.manifest_version)
|
||||
upload_huggingface(info, result, workspace, args.hf_bucket, args.manifest_version,
|
||||
manifest, not args.no_onnx_upload, source)
|
||||
manifest = resources_repo / f"model_names_{args.manifest_version}.json"
|
||||
manifest = upload_huggingface(info, result, workspace, args.hf_bucket, args.manifest_version,
|
||||
manifest, not args.no_onnx_upload, source)
|
||||
push_github(info, result, resources_repo, args.manifest_version, manifest, args.resources_branch, args.force)
|
||||
print("\nRelease complete.")
|
||||
print(f" local artifact: {result['path']}")
|
||||
|
||||
@@ -3,8 +3,20 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts import model_release
|
||||
from scripts.model_release import parse_lfs_pointer, parse_pasted_release, runtime_file, update_manifest
|
||||
from scripts.model_release import (
|
||||
ReleaseError,
|
||||
parse_lfs_pointer,
|
||||
parse_pasted_release,
|
||||
prepare_huggingface_manifest,
|
||||
refresh_huggingface_manifest,
|
||||
runtime_file,
|
||||
update_manifest,
|
||||
upload_huggingface,
|
||||
validate_manifest_update,
|
||||
)
|
||||
|
||||
|
||||
RELEASE_TEXT = """
|
||||
@@ -95,3 +107,138 @@ def test_update_manifest_replaces_one_entry(tmp_path: Path):
|
||||
assert entry["artifact_size"] == 123
|
||||
assert entry["artifact_chunk_count"] == 2
|
||||
assert entry["uses_external_gpu"]
|
||||
|
||||
|
||||
def test_prepare_manifest_refreshes_live_copy_before_adding_model(tmp_path: Path, monkeypatch):
|
||||
manifest = tmp_path / "model_names_v25.json"
|
||||
manifest.write_text(json.dumps({"models": [{"id": "stale"}]}) + "\n")
|
||||
live_payload = {
|
||||
"models": [{
|
||||
"id": "small-model",
|
||||
"model_lab_eligible": True,
|
||||
"accelerator_artifacts": {
|
||||
"chestnut": {
|
||||
"artifact_filename": "small-model_driving_chestnut_tinygrad.pkl",
|
||||
"artifact_sha256": "b" * 64,
|
||||
"execution_device": "AMD",
|
||||
},
|
||||
},
|
||||
}],
|
||||
}
|
||||
|
||||
def fake_refresh(path, bucket):
|
||||
assert bucket == "StarPilot-Driving/StarPilot-Resources"
|
||||
path.write_text(json.dumps(live_payload) + "\n")
|
||||
return live_payload
|
||||
|
||||
monkeypatch.setattr(model_release, "refresh_huggingface_manifest", fake_refresh)
|
||||
info = parse_pasted_release(RELEASE_TEXT, "bmrlnapv4", "v16")
|
||||
prepared = prepare_huggingface_manifest(
|
||||
manifest,
|
||||
info,
|
||||
{"size": 123, "sha256": "a" * 64, "chunk_count": 2},
|
||||
"StarPilot-Driving/StarPilot-Resources",
|
||||
"v25",
|
||||
)
|
||||
|
||||
models = {entry["id"]: entry for entry in json.loads(prepared.read_text())["models"]}
|
||||
assert set(models) == {"small-model", "bmrlnapv4"}
|
||||
assert models["small-model"] == live_payload["models"][0]
|
||||
assert models["bmrlnapv4"]["artifact_sha256"] == "a" * 64
|
||||
|
||||
|
||||
def test_manifest_guard_rejects_unrelated_accelerator_metadata_regression():
|
||||
chestnut = {"execution_device": "AMD", "artifact_sha256": "a" * 64}
|
||||
before = {"models": [
|
||||
{"id": "keep", "accelerator_artifacts": {"chestnut": chestnut}},
|
||||
{"id": "replace", "accelerator_artifacts": {"chestnut": chestnut}},
|
||||
]}
|
||||
after = {"models": [{"id": "keep"}, {"id": "replace"}]}
|
||||
|
||||
with pytest.raises(ReleaseError, match="keep:chestnut"):
|
||||
validate_manifest_update(before, after, "replace")
|
||||
|
||||
validate_manifest_update(
|
||||
{"models": [{"id": "replace", "accelerator_artifacts": {"chestnut": chestnut}}]},
|
||||
{"models": [{"id": "replace"}]},
|
||||
"replace",
|
||||
)
|
||||
|
||||
with pytest.raises(ReleaseError, match="unrelated model entries: keep"):
|
||||
validate_manifest_update(
|
||||
{"models": [{"id": "keep", "model_lab_eligible": True}]},
|
||||
{"models": [{"id": "keep", "model_lab_eligible": False}, {"id": "replace"}]},
|
||||
"replace",
|
||||
)
|
||||
|
||||
|
||||
def test_refresh_manifest_is_atomic_and_uses_hugging_face_bucket(tmp_path: Path, monkeypatch):
|
||||
manifest = tmp_path / "model_names_v25.json"
|
||||
manifest.write_text(json.dumps({"models": [{"id": "stale"}]}) + "\n")
|
||||
live_payload = {"models": [{"id": "live"}]}
|
||||
commands = []
|
||||
|
||||
monkeypatch.setattr(model_release, "find_hf", lambda: "/usr/bin/hf")
|
||||
|
||||
def fake_run(command, **kwargs):
|
||||
commands.append(command)
|
||||
Path(command[4]).write_text(json.dumps(live_payload) + "\n")
|
||||
|
||||
monkeypatch.setattr(model_release, "run", fake_run)
|
||||
assert refresh_huggingface_manifest(manifest, "owner/resources") == live_payload
|
||||
assert json.loads(manifest.read_text()) == live_payload
|
||||
assert commands[0][0:4] == [
|
||||
"/usr/bin/hf",
|
||||
"buckets",
|
||||
"cp",
|
||||
"hf://buckets/owner/resources/manifests/model_names_v25.json",
|
||||
]
|
||||
|
||||
|
||||
def test_refresh_manifest_keeps_local_copy_when_live_json_is_invalid(tmp_path: Path, monkeypatch):
|
||||
manifest = tmp_path / "model_names_v25.json"
|
||||
original = {"models": [{"id": "safe"}]}
|
||||
manifest.write_text(json.dumps(original) + "\n")
|
||||
monkeypatch.setattr(model_release, "find_hf", lambda: "/usr/bin/hf")
|
||||
monkeypatch.setattr(model_release, "run", lambda command, **kwargs: Path(command[4]).write_text("{"))
|
||||
|
||||
with pytest.raises(ReleaseError, match="Invalid live Hugging Face manifest"):
|
||||
refresh_huggingface_manifest(manifest, "owner/resources")
|
||||
assert json.loads(manifest.read_text()) == original
|
||||
|
||||
|
||||
def test_upload_refreshes_manifest_after_artifacts(tmp_path: Path, monkeypatch):
|
||||
artifact_dir = tmp_path / "artifacts"
|
||||
artifact_dir.mkdir()
|
||||
manifest = tmp_path / "resources" / "model_names_v25.json"
|
||||
manifest.parent.mkdir()
|
||||
manifest.write_text(json.dumps({"models": [{"id": "old"}]}) + "\n")
|
||||
source = tmp_path / "model.onnx"
|
||||
calls = []
|
||||
info = parse_pasted_release(RELEASE_TEXT, "bmrlnapv4", "v16")
|
||||
result = {
|
||||
"path": str(artifact_dir),
|
||||
"files": ["bmrlnapv4_driving_tinygrad.pkl"],
|
||||
"size": 123,
|
||||
"sha256": "a" * 64,
|
||||
"chunk_count": 0,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(model_release, "hf_copy", lambda source, bucket, remote: calls.append(("copy", remote)))
|
||||
|
||||
def fake_prepare(path, release_info, release_result, bucket, version):
|
||||
calls.append(("prepare", path.name))
|
||||
return path
|
||||
|
||||
monkeypatch.setattr(model_release, "prepare_huggingface_manifest", fake_prepare)
|
||||
returned = upload_huggingface(
|
||||
info, result, tmp_path, "owner/resources", "v25", manifest, True, source,
|
||||
)
|
||||
|
||||
assert returned == manifest
|
||||
assert calls == [
|
||||
("copy", "models/v25/bmrlnapv4/bmrlnapv4_driving_tinygrad.pkl"),
|
||||
("copy", "onnx/bmrlnapv4/model.onnx"),
|
||||
("prepare", "model_names_v25.json"),
|
||||
("copy", "manifests/model_names_v25.json"),
|
||||
]
|
||||
|
||||
@@ -262,7 +262,7 @@ class LongControl:
|
||||
output_accel = min(output_accel, 0.0)
|
||||
output_accel -= starpilot_toggles.stoppingDecelRate * DT_CTRL
|
||||
output_accel = self.vehicle_tuning.shape_stopping_accel(
|
||||
output_accel, a_target, should_stop, CS.vEgo, has_lead, starpilot_toggles.stopAccel,
|
||||
output_accel, a_target, should_stop, CS.vEgo, has_lead, starpilot_toggles.stopAccel, leads=leads,
|
||||
)
|
||||
output_accel = self._apply_moving_stop_target_follow(output_accel, a_target, should_stop, CS, starpilot_toggles)
|
||||
self.reset(preserve_stop_release=True)
|
||||
|
||||
@@ -3,6 +3,7 @@ import numpy as np
|
||||
from opendbc.car.gm.values import CAR, GMFlags
|
||||
from opendbc.car.subaru.values import CAR as SUBARU_CAR
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA_CAR
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN_CAR
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
@@ -63,6 +64,12 @@ HYUNDAI_ELANTRA_FINAL_STOP_URGENCY_MARGIN = 0.45
|
||||
HYUNDAI_SANTA_FE_FINAL_STOP_MAX_SPEED = 1.0
|
||||
HYUNDAI_SANTA_FE_FINAL_STOP_CAP_BP = [0.0, 0.2, 0.5, HYUNDAI_SANTA_FE_FINAL_STOP_MAX_SPEED]
|
||||
HYUNDAI_SANTA_FE_FINAL_STOP_CAP_V = [-0.25, -0.30, -0.50, -0.90]
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_MAX_SPEED = 4.5
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_MIN_DISTANCE = 5.0
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_MIN_TTC = 4.0
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_MAX_CLOSING_SPEED = 1.5
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_CAP_BP = [0.0, 0.5, 1.0, 2.0, 3.5, VOLKSWAGEN_TAOS_COMFORT_STOP_MAX_SPEED]
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_CAP_V = [-0.45, -0.55, -0.65, -0.80, -0.95, -1.10]
|
||||
|
||||
|
||||
def get_bolt_acc_pedal_friction_bias(output_accel, a_target, v_ego):
|
||||
@@ -152,6 +159,10 @@ class LongControlVehicleTuning:
|
||||
self.is_hyundai_santa_fe_2022 = bool(
|
||||
CP.brand == "hyundai" and str(getattr(CP, "carFingerprint", "")) == "HYUNDAI_SANTA_FE_2022"
|
||||
)
|
||||
self.is_volkswagen_taos = bool(
|
||||
CP.brand == "volkswagen" and
|
||||
str(getattr(CP, "carFingerprint", "")) == str(VOLKSWAGEN_CAR.VOLKSWAGEN_TAOS_MK1)
|
||||
)
|
||||
self.is_bolt_acc_pedal_friction_car = bool(
|
||||
CP.brand == "gm" and
|
||||
CP.enableGasInterceptorDEPRECATED and
|
||||
@@ -172,8 +183,32 @@ class LongControlVehicleTuning:
|
||||
self.bolt_start_handoff_frames = 0
|
||||
self.subaru_stop_release_frames = 0
|
||||
|
||||
def shape_stopping_accel(self, output_accel, a_target, should_stop, v_ego, has_lead, stop_accel):
|
||||
def shape_stopping_accel(self, output_accel, a_target, should_stop, v_ego, has_lead, stop_accel, leads=None):
|
||||
"""Shape low-speed stop braking without overriding urgent targets."""
|
||||
if self.is_volkswagen_taos and should_stop and has_lead and v_ego < VOLKSWAGEN_TAOS_COMFORT_STOP_MAX_SPEED:
|
||||
comfort_lead = next((
|
||||
lead for lead in (leads or ())
|
||||
if bool(getattr(lead, "status", False)) and
|
||||
abs(float(getattr(lead, "yRel", 0.0))) <= 1.75 and
|
||||
float(getattr(lead, "dRel", 0.0)) > 0.0
|
||||
), None)
|
||||
if comfort_lead is not None:
|
||||
lead_distance = float(getattr(comfort_lead, "dRel", 0.0))
|
||||
lead_speed = max(0.0, float(getattr(comfort_lead, "vLead", 0.0)))
|
||||
closing_speed = max(0.0, float(v_ego) - lead_speed)
|
||||
ttc = lead_distance / max(closing_speed, 0.1) if closing_speed > 0.1 else float("inf")
|
||||
if (
|
||||
lead_distance >= VOLKSWAGEN_TAOS_COMFORT_STOP_MIN_DISTANCE and
|
||||
ttc >= VOLKSWAGEN_TAOS_COMFORT_STOP_MIN_TTC and
|
||||
closing_speed <= VOLKSWAGEN_TAOS_COMFORT_STOP_MAX_CLOSING_SPEED
|
||||
):
|
||||
comfort_cap = float(interp(
|
||||
v_ego,
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_CAP_BP,
|
||||
VOLKSWAGEN_TAOS_COMFORT_STOP_CAP_V,
|
||||
))
|
||||
return max(float(output_accel), comfort_cap)
|
||||
|
||||
if (
|
||||
self.is_hyundai_elantra_2021 and
|
||||
should_stop and
|
||||
|
||||
@@ -8,6 +8,7 @@ import openpilot.selfdrive.controls.lib.longcontrol_vehicle_tunes as vehicle_tun
|
||||
from opendbc.car.gm.values import CAR, GMFlags
|
||||
from opendbc.car.subaru.values import CAR as SUBARU_CAR
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA_CAR
|
||||
from opendbc.car.volkswagen.values import CAR as VOLKSWAGEN_CAR
|
||||
from openpilot.common.realtime import DT_CTRL
|
||||
from openpilot.selfdrive.controls.lib.longcontrol import (
|
||||
LongControl,
|
||||
@@ -1236,6 +1237,34 @@ def test_santa_fe_final_stop_cap_softens_only_last_kmh():
|
||||
assert tuning.shape_stopping_accel(-2.0, 0.3, False, 0.2, False, -2.0) == pytest.approx(-2.0)
|
||||
|
||||
|
||||
def test_taos_comfort_stop_cap_softens_non_urgent_moving_lead():
|
||||
CP = make_longcontrol_cp(
|
||||
brand="volkswagen",
|
||||
carFingerprint=VOLKSWAGEN_CAR.VOLKSWAGEN_TAOS_MK1,
|
||||
)
|
||||
tuning = vehicle_tunes.LongControlVehicleTuning(CP)
|
||||
moving_lead = SimpleNamespace(status=True, dRel=7.0, vLead=2.6, yRel=0.0)
|
||||
|
||||
output = tuning.shape_stopping_accel(
|
||||
-1.88, -2.12, True, 3.4, True, -0.55, leads=(moving_lead,)
|
||||
)
|
||||
|
||||
assert output == pytest.approx(-0.94)
|
||||
|
||||
|
||||
def test_taos_comfort_stop_cap_preserves_urgent_lead_braking():
|
||||
CP = make_longcontrol_cp(
|
||||
brand="volkswagen",
|
||||
carFingerprint=VOLKSWAGEN_CAR.VOLKSWAGEN_TAOS_MK1,
|
||||
)
|
||||
tuning = vehicle_tunes.LongControlVehicleTuning(CP)
|
||||
stopped_lead = SimpleNamespace(status=True, dRel=6.0, vLead=0.2, yRel=0.0)
|
||||
|
||||
assert tuning.shape_stopping_accel(
|
||||
-1.88, -2.12, True, 3.4, True, -0.55, leads=(stopped_lead,)
|
||||
) == pytest.approx(-1.88)
|
||||
|
||||
|
||||
def test_toyota_sienna_target_filter_smooths_mild_high_speed_handoffs():
|
||||
CP = make_longcontrol_cp(brand="toyota", carFingerprint=TOYOTA_CAR.TOYOTA_SIENNA_4TH_GEN)
|
||||
tuning = vehicle_tunes.LongControlVehicleTuning(CP)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from enum import IntEnum
|
||||
|
||||
import pyray as rl
|
||||
|
||||
@@ -11,6 +12,12 @@ _BORDER_ROUNDNESS = 0.12
|
||||
_BORDER_RADIUS_MULTIPLE = 3.0
|
||||
|
||||
|
||||
class LeadInfoMode(IntEnum):
|
||||
OFF = 0
|
||||
DISTANCE = 1
|
||||
SPEED = 2
|
||||
|
||||
|
||||
def get_border_roundness(rect: rl.Rectangle, border_width: float) -> float:
|
||||
"""Keep a rectangular camera inset inside the rounded frame at thin widths."""
|
||||
min_dimension = max(1.0, min(rect.width, rect.height))
|
||||
@@ -43,3 +50,16 @@ def lead_indicator_enabled(params: Params | None = None, *, hide_by_default: boo
|
||||
if active_params.get("HideLeadMarker") is None:
|
||||
return not hide_by_default
|
||||
return not active_params.get_bool("HideLeadMarker")
|
||||
|
||||
|
||||
def lead_info_mode(params: Params | None = None) -> LeadInfoMode:
|
||||
active_params = params if params is not None else Params()
|
||||
if not active_params.get_bool("LeadInfo"):
|
||||
return LeadInfoMode.OFF
|
||||
|
||||
try:
|
||||
mode = LeadInfoMode(active_params.get_int("LeadInfoMode", return_default=True, default=LeadInfoMode.SPEED))
|
||||
except ValueError:
|
||||
mode = LeadInfoMode.SPEED
|
||||
|
||||
return LeadInfoMode.SPEED if mode == LeadInfoMode.OFF else mode
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.ui.lib.starpilot_visuals import lead_indicator_enabled
|
||||
from openpilot.selfdrive.ui.lib.starpilot_visuals import LeadInfoMode, lead_indicator_enabled, lead_info_mode
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl, BigToggle
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigMultiOptionDialog
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.widgets.scroller import NavScroller
|
||||
|
||||
CAMERA_VIEW_LABELS = ["Auto", "Driver", "Standard", "Wide", "None"]
|
||||
LEAD_INFO_LABELS = {
|
||||
LeadInfoMode.OFF: "Off",
|
||||
LeadInfoMode.DISTANCE: "Distance",
|
||||
LeadInfoMode.SPEED: "Speed",
|
||||
}
|
||||
|
||||
|
||||
class CameraViewBigButton(BigButton):
|
||||
@@ -53,6 +58,37 @@ class LeadIndicatorBigButton(BigToggle):
|
||||
self.set_checked(lead_indicator_enabled(self.params, hide_by_default=True))
|
||||
|
||||
|
||||
class LeadInfoBigButton(BigButton):
|
||||
def __init__(self):
|
||||
super().__init__("lead info", "", gui_app.texture("icons_mici/onroad/eye_fill.png", 64, 64))
|
||||
self.params = Params()
|
||||
self.set_click_callback(self._show_selector)
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
self.set_value(LEAD_INFO_LABELS[lead_info_mode(self.params)].lower())
|
||||
|
||||
def _show_selector(self):
|
||||
current_mode = lead_info_mode(self.params)
|
||||
options = list(LEAD_INFO_LABELS.values())
|
||||
dialog_holder: dict[str, BigMultiOptionDialog] = {}
|
||||
|
||||
def on_confirm():
|
||||
selected = dialog_holder["dialog"].get_selected_option()
|
||||
try:
|
||||
selected_mode = next(mode for mode, label in LEAD_INFO_LABELS.items() if label == selected)
|
||||
except StopIteration:
|
||||
gui_app.push_widget(BigDialog("", "Invalid lead info mode"))
|
||||
return
|
||||
self.params.put_int("LeadInfoMode", int(selected_mode))
|
||||
self.params.put_bool("LeadInfo", selected_mode != LeadInfoMode.OFF)
|
||||
self.refresh()
|
||||
|
||||
dialog = BigMultiOptionDialog(options=options, default=LEAD_INFO_LABELS[current_mode], right_btn_callback=on_confirm)
|
||||
dialog_holder["dialog"] = dialog
|
||||
gui_app.push_widget(dialog)
|
||||
|
||||
|
||||
class VisualsLayoutMici(NavScroller):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -63,7 +99,7 @@ class VisualsLayoutMici(NavScroller):
|
||||
self._torque_bar_btn = BigParamControl("torque bar", "EnableTorqueBarWidget")
|
||||
self._rainbow_path_btn = BigParamControl("rainbow road", "RainbowPath")
|
||||
self._lead_indicator_btn = LeadIndicatorBigButton()
|
||||
self._lead_info_btn = BigParamControl("show lead speed", "LeadInfo")
|
||||
self._lead_info_btn = LeadInfoBigButton()
|
||||
self._speed_limit_signs_btn = BigParamControl("show speed limits", "ShowSpeedLimits")
|
||||
self._slc_confirmation_btn = BigParamControl("confirm new speed limits", "SLCConfirmation")
|
||||
self._slc_confirmation_lower_btn = BigParamControl("confirm lower limits", "SLCConfirmationLower")
|
||||
@@ -95,6 +131,7 @@ class VisualsLayoutMici(NavScroller):
|
||||
def _refresh(self):
|
||||
self._camera_view_btn.refresh()
|
||||
self._lead_indicator_btn.refresh()
|
||||
self._lead_info_btn.refresh()
|
||||
self._lead_info_btn.set_enabled(lead_indicator_enabled(self._lead_info_btn.params, hide_by_default=True))
|
||||
confirmation_enabled = self._slc_confirmation_btn.params.get_bool("SLCConfirmation")
|
||||
self._slc_confirmation_lower_btn.set_visible(confirmation_enabled)
|
||||
|
||||
@@ -10,7 +10,7 @@ from openpilot.selfdrive.controls.lib.lane_centering import get_lane_centering_v
|
||||
from openpilot.selfdrive.locationd.calibrationd import HEIGHT_INIT
|
||||
from openpilot.selfdrive.ui.lib.starpilot_theme import get_param_color, get_theme_color, get_visual_color, is_stock_color_scheme, with_alpha
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.rainbow_path import RainbowPath
|
||||
from openpilot.selfdrive.ui.lib.starpilot_visuals import blend_colors, lead_indicator_enabled
|
||||
from openpilot.selfdrive.ui.lib.starpilot_visuals import LeadInfoMode, blend_colors, lead_indicator_enabled, lead_info_mode
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
|
||||
from openpilot.selfdrive.ui.mici.onroad.starpilot_status import get_border_color
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
@@ -67,7 +67,7 @@ class ModelRenderer(Widget):
|
||||
self._lane_line_probs = np.zeros(4, dtype=np.float32)
|
||||
self._road_edge_stds = np.zeros(2, dtype=np.float32)
|
||||
self._lead_vehicles = [LeadVehicle(), LeadVehicle()]
|
||||
self._lead_info_enabled = False
|
||||
self._lead_info_mode = LeadInfoMode.OFF
|
||||
self._path_offset_z = HEIGHT_INIT[0]
|
||||
|
||||
# Initialize ModelPoints objects
|
||||
@@ -138,7 +138,7 @@ class ModelRenderer(Widget):
|
||||
model = sm['modelV2']
|
||||
radar_state = sm['radarState'] if sm.valid['radarState'] else None
|
||||
lead_one = radar_state.leadOne if radar_state else None
|
||||
self._lead_info_enabled = self._params.get_bool("LeadInfo")
|
||||
self._lead_info_mode = lead_info_mode(self._params)
|
||||
render_lead_indicator = self._should_render_lead_indicator(radar_state)
|
||||
|
||||
# Update model data when needed
|
||||
@@ -512,8 +512,15 @@ class ModelRenderer(Widget):
|
||||
rl.draw_triangle_fan(lead.chevron, len(lead.chevron), with_alpha(lead_color, lead.fill_alpha))
|
||||
|
||||
lead_one = radar_state.leadOne
|
||||
if self._lead_info_enabled and lead_one and lead_one.status:
|
||||
self._draw_lead_speed(lead_one)
|
||||
if self._lead_info_mode != LeadInfoMode.OFF and lead_one and lead_one.status:
|
||||
self._draw_lead_info(lead_one)
|
||||
|
||||
@staticmethod
|
||||
def _format_lead_distance(lead_distance: float, is_metric: bool, use_si_metrics: bool) -> str:
|
||||
lead_distance = max(float(lead_distance), 0.0)
|
||||
if is_metric or use_si_metrics:
|
||||
return f"{round(lead_distance)} m"
|
||||
return f"{round(lead_distance * CV.METER_TO_FOOT)} ft"
|
||||
|
||||
@staticmethod
|
||||
def _format_lead_speed(lead_speed: float, is_metric: bool, use_si_metrics: bool) -> str:
|
||||
@@ -524,14 +531,16 @@ class ModelRenderer(Widget):
|
||||
return f"{round(lead_speed * CV.MS_TO_KPH)} km/h"
|
||||
return f"{round(lead_speed * CV.MS_TO_MPH)} mph"
|
||||
|
||||
def _draw_lead_speed(self, lead_data) -> None:
|
||||
def _format_lead_info(self, lead_data, is_metric: bool, use_si_metrics: bool) -> str:
|
||||
if self._lead_info_mode == LeadInfoMode.DISTANCE:
|
||||
return self._format_lead_distance(getattr(lead_data, "dRel", 0.0), is_metric, use_si_metrics)
|
||||
return self._format_lead_speed(getattr(lead_data, "vLead", 0.0), is_metric, use_si_metrics)
|
||||
|
||||
def _draw_lead_info(self, lead_data) -> None:
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.path import _draw_text_with_outline
|
||||
|
||||
text = self._format_lead_speed(
|
||||
getattr(lead_data, "vLead", 0.0),
|
||||
ui_state.is_metric,
|
||||
ui_state.starpilot_toggles.get("UseSiMetrics", False),
|
||||
)
|
||||
use_si_metrics = ui_state.starpilot_toggles.get("UseSiMetrics", False)
|
||||
text = self._format_lead_info(lead_data, ui_state.is_metric, use_si_metrics)
|
||||
font = gui_app.font(FontWeight.SEMI_BOLD)
|
||||
font_size = 40
|
||||
text_size = measure_text_cached(font, text, font_size)
|
||||
|
||||
@@ -6,9 +6,10 @@ import openpilot.selfdrive.ui.mici.onroad.model_renderer as model_renderer
|
||||
|
||||
|
||||
class _FakeParams:
|
||||
def __init__(self, enabled: bool, lead_info: bool = False):
|
||||
def __init__(self, enabled: bool, lead_info: bool = False, lead_info_mode: int = model_renderer.LeadInfoMode.SPEED):
|
||||
self.enabled = enabled
|
||||
self.lead_info = lead_info
|
||||
self.lead_info_mode = lead_info_mode
|
||||
|
||||
def get(self, key):
|
||||
assert key == "HideLeadMarker"
|
||||
@@ -21,6 +22,10 @@ class _FakeParams:
|
||||
return self.lead_info
|
||||
raise AssertionError(key)
|
||||
|
||||
def get_int(self, key, **_kwargs):
|
||||
assert key == "LeadInfoMode"
|
||||
return self.lead_info_mode
|
||||
|
||||
|
||||
def test_lead_indicator_renders_in_aol_without_longitudinal_control(monkeypatch):
|
||||
monkeypatch.setattr(model_renderer, "ui_state", SimpleNamespace(always_on_lateral_active=True))
|
||||
@@ -47,17 +52,59 @@ def test_lead_indicator_still_honors_disabled_setting():
|
||||
(False, True, "10 m/s"),
|
||||
],
|
||||
)
|
||||
def test_lead_speed_uses_c3_units(is_metric, use_si_metrics, expected):
|
||||
def test_lead_speed_uses_selected_units(is_metric, use_si_metrics, expected):
|
||||
assert model_renderer.ModelRenderer._format_lead_speed(10.0, is_metric, use_si_metrics) == expected
|
||||
|
||||
|
||||
def test_lead_metrics_draw_only_speed_when_enabled(monkeypatch):
|
||||
@pytest.mark.parametrize(
|
||||
("is_metric", "use_si_metrics", "expected"),
|
||||
[
|
||||
(False, False, "33 ft"),
|
||||
(True, False, "10 m"),
|
||||
(False, True, "10 m"),
|
||||
],
|
||||
)
|
||||
def test_lead_distance_uses_selected_units(is_metric, use_si_metrics, expected):
|
||||
assert model_renderer.ModelRenderer._format_lead_distance(10.0, is_metric, use_si_metrics) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("lead_info", "configured_mode", "expected"),
|
||||
[
|
||||
(False, model_renderer.LeadInfoMode.SPEED, model_renderer.LeadInfoMode.OFF),
|
||||
(True, model_renderer.LeadInfoMode.DISTANCE, model_renderer.LeadInfoMode.DISTANCE),
|
||||
(True, model_renderer.LeadInfoMode.SPEED, model_renderer.LeadInfoMode.SPEED),
|
||||
(True, model_renderer.LeadInfoMode.OFF, model_renderer.LeadInfoMode.SPEED),
|
||||
(True, 99, model_renderer.LeadInfoMode.SPEED),
|
||||
],
|
||||
)
|
||||
def test_lead_info_mode_preserves_legacy_speed_behavior(lead_info, configured_mode, expected):
|
||||
params = _FakeParams(enabled=True, lead_info=lead_info, lead_info_mode=configured_mode)
|
||||
assert model_renderer.lead_info_mode(params) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "expected"),
|
||||
[
|
||||
(model_renderer.LeadInfoMode.DISTANCE, "82 ft"),
|
||||
(model_renderer.LeadInfoMode.SPEED, "27 mph"),
|
||||
],
|
||||
)
|
||||
def test_lead_info_formats_selected_metric(mode, expected):
|
||||
renderer = object.__new__(model_renderer.ModelRenderer)
|
||||
renderer._lead_info_mode = mode
|
||||
lead = SimpleNamespace(dRel=25.0, vLead=12.0)
|
||||
|
||||
assert renderer._format_lead_info(lead, is_metric=False, use_si_metrics=False) == expected
|
||||
|
||||
|
||||
def test_lead_metrics_draw_selected_value_when_enabled(monkeypatch):
|
||||
drawn_metrics = []
|
||||
monkeypatch.setattr(model_renderer, "get_theme_color", lambda *_args: model_renderer.rl.RED)
|
||||
monkeypatch.setattr(model_renderer.rl, "draw_triangle_fan", lambda *_args: None)
|
||||
|
||||
renderer = object.__new__(model_renderer.ModelRenderer)
|
||||
renderer._lead_info_enabled = True
|
||||
renderer._lead_info_mode = model_renderer.LeadInfoMode.DISTANCE
|
||||
renderer._lead_vehicles = [
|
||||
model_renderer.LeadVehicle(
|
||||
glow=[(1.0, 2.0)] * 3,
|
||||
@@ -66,8 +113,8 @@ def test_lead_metrics_draw_only_speed_when_enabled(monkeypatch):
|
||||
),
|
||||
model_renderer.LeadVehicle(),
|
||||
]
|
||||
renderer._draw_lead_speed = drawn_metrics.append
|
||||
lead_one = SimpleNamespace(status=True, vLead=10.0)
|
||||
renderer._draw_lead_info = drawn_metrics.append
|
||||
lead_one = SimpleNamespace(status=True, dRel=25.0, vLead=10.0)
|
||||
|
||||
renderer._draw_lead_indicator(SimpleNamespace(leadOne=lead_one, leadTwo=SimpleNamespace(status=False)))
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ from openpilot.starpilot.assets.model_manager import (
|
||||
external_gpu_available,
|
||||
get_model_profile,
|
||||
is_builtin_model_key,
|
||||
model_accelerator_catalog_artifact_metadata,
|
||||
model_accelerator_artifact_filename,
|
||||
model_key_aliases,
|
||||
model_uses_external_gpu,
|
||||
@@ -7424,18 +7423,15 @@ def setup(app):
|
||||
metadata = artifact_metadata.get(canonical_key, {})
|
||||
metadata = metadata if isinstance(metadata, dict) else {}
|
||||
small_model = is_small_model_metadata({**metadata, "uses_external_gpu": requires_external_gpu})
|
||||
lab_compatible = model_lab_manifest_eligible({**metadata, "uses_external_gpu": requires_external_gpu}, model_version)
|
||||
lab_eligible = model_lab_manifest_eligible({**metadata, "uses_external_gpu": requires_external_gpu}, model_version)
|
||||
accelerator_artifacts = metadata.get("accelerator_artifacts", {})
|
||||
accelerator_artifacts = accelerator_artifacts if isinstance(accelerator_artifacts, dict) else {}
|
||||
chestnut_artifact = accelerator_artifacts.get("chestnut", {})
|
||||
chestnut_artifact = chestnut_artifact if isinstance(chestnut_artifact, dict) else {}
|
||||
if not chestnut_artifact:
|
||||
chestnut_artifact = model_accelerator_catalog_artifact_metadata(canonical_key)
|
||||
lab_artifact_available = (
|
||||
bool(chestnut_artifact)
|
||||
and str(chestnut_artifact.get("execution_device") or chestnut_artifact.get("device") or "").strip().upper() == "AMD"
|
||||
)
|
||||
lab_eligible = lab_compatible and lab_artifact_available
|
||||
lab_artifact_path = MODELS_PATH / model_accelerator_artifact_filename(canonical_key)
|
||||
lab_artifact_installed = lab_artifact_available and file_chunked_exists(lab_artifact_path)
|
||||
existing = models_by_key.get(canonical_key)
|
||||
@@ -7482,11 +7478,6 @@ def setup(app):
|
||||
existing["modelLabArtifactInstalled"] = existing["modelLabArtifactInstalled"] and lab_artifact_installed
|
||||
|
||||
default_key = _default_model_key()
|
||||
default_chestnut_artifact = model_accelerator_catalog_artifact_metadata(default_key)
|
||||
default_lab_artifact_available = (
|
||||
bool(default_chestnut_artifact)
|
||||
and str(default_chestnut_artifact.get("execution_device") or default_chestnut_artifact.get("device") or "").strip().upper() == "AMD"
|
||||
)
|
||||
default_entry = models_by_key.setdefault(default_key, {
|
||||
"value": default_key,
|
||||
"label": _default_model_name(),
|
||||
@@ -7498,13 +7489,9 @@ def setup(app):
|
||||
"small": True,
|
||||
"modelSize": "small (inferred)",
|
||||
"manifestDeclaredSize": False,
|
||||
"modelLabEligible": default_lab_artifact_available and model_lab_manifest_eligible(
|
||||
artifact_metadata.get(default_key, {}), _default_model_version()
|
||||
),
|
||||
"modelLabArtifactAvailable": default_lab_artifact_available,
|
||||
"modelLabArtifactInstalled": default_lab_artifact_available and file_chunked_exists(
|
||||
MODELS_PATH / model_accelerator_artifact_filename(default_key)
|
||||
),
|
||||
"modelLabEligible": model_lab_manifest_eligible(artifact_metadata.get(default_key, {}), _default_model_version()),
|
||||
"modelLabArtifactAvailable": False,
|
||||
"modelLabArtifactInstalled": False,
|
||||
"released": "",
|
||||
"builtin": True,
|
||||
"communityFavorite": default_key in community_favorites,
|
||||
|
||||
@@ -166,6 +166,7 @@ LateralTune
|
||||
LeadDepartingAlert
|
||||
LeadDetectionThreshold
|
||||
LeadInfo
|
||||
LeadInfoMode
|
||||
LiveDelay
|
||||
LiveParameters
|
||||
LiveParametersV2
|
||||
|
||||
Reference in New Issue
Block a user