mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-23 17:23:44 +08:00
Comover
This commit is contained in:
@@ -220,6 +220,7 @@ struct StarPilotPlan @0xf98d843bfd7004a3 {
|
||||
disableThrottle @35 :Bool;
|
||||
trackingLead @36 :Bool;
|
||||
stopSignConfirmed @37 :Bool;
|
||||
pulseGlideCoasting @38 :Bool; # developer-only P&G phase for on-road status UI
|
||||
}
|
||||
|
||||
struct StarPilotRadarState @0xb86e6369214c01c8 {
|
||||
|
||||
@@ -9,10 +9,12 @@ from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning
|
||||
CANFD_RADAR_LIVE_LONGITUDINAL_CAR, kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
VisualAlert = structs.CarControl.HUDControl.VisualAlert
|
||||
LongCtrlState = structs.CarControl.Actuators.LongControlState
|
||||
@@ -79,7 +81,10 @@ BLINDSPOT_WARNING_SOUND_SAMPLES = 36
|
||||
|
||||
def egmp_dynamic_longitudinal_tuning(CP) -> bool:
|
||||
return CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.KIA_EV9, CAR.HYUNDAI_IONIQ_5_PE) or \
|
||||
kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", ""))
|
||||
kia_ev6_gt_line_longitudinal_tuning(
|
||||
CP.carFingerprint, getattr(CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
)
|
||||
|
||||
|
||||
def get_canfd_scc_decel_step(CP) -> float:
|
||||
@@ -87,7 +92,10 @@ def get_canfd_scc_decel_step(CP) -> float:
|
||||
|
||||
|
||||
def should_reset_ev6_gt_line_longitudinal_tuning(CP, long_control_state: LongCtrlState) -> bool:
|
||||
return kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, getattr(CP, "carVin", "")) and \
|
||||
return kia_ev6_gt_line_longitudinal_tuning(
|
||||
CP.carFingerprint, getattr(CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
) and \
|
||||
long_control_state == LongCtrlState.off
|
||||
|
||||
|
||||
@@ -631,7 +639,10 @@ class CarController(CarControllerBase):
|
||||
|
||||
use_egmp_dynamic_long_tuning = egmp_dynamic_longitudinal_tuning(self.CP) and self.long_active_ecu and \
|
||||
actuators.longControlState in (LongCtrlState.starting, LongCtrlState.pid, LongCtrlState.stopping)
|
||||
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(self.CP.carFingerprint, getattr(self.CP, "carVin", ""))
|
||||
is_ev6_gt_line = kia_ev6_gt_line_longitudinal_tuning(
|
||||
self.CP.carFingerprint, getattr(self.CP, "carVin", ""),
|
||||
testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID),
|
||||
)
|
||||
is_ccnc_angle_long = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR
|
||||
if is_ccnc_angle_long and (self._ev9_long_tuning.stop_request or not CC.enabled or CC.cruiseControl.override):
|
||||
self._ioniq_6_long_tuning = reset_egmp_longitudinal_tuning(self._ioniq_6_long_tuning)
|
||||
|
||||
@@ -12,13 +12,15 @@ from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
|
||||
CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR, \
|
||||
HyundaiStarPilotSafetyFlags, \
|
||||
hyundai_cancel_button_enables_cruise, \
|
||||
kia_ev6_gt_line_longitudinal_tuning
|
||||
kia_ev6_gt_line_longitudinal_tuning, \
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
|
||||
from opendbc.car.hyundai.radar_interface import get_radar_track_config, radar_tracks_available
|
||||
from opendbc.car.interfaces import CarInterfaceBase, ACCEL_MIN
|
||||
from opendbc.car.disable_ecu import disable_ecu, ecu_log
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
from openpilot.starpilot.common.testing_grounds import testing_ground
|
||||
|
||||
ButtonType = structs.CarState.ButtonEvent.Type
|
||||
Ecu = structs.CarParams.Ecu
|
||||
@@ -106,7 +108,8 @@ class CarInterface(CarInterfaceBase):
|
||||
|
||||
@staticmethod
|
||||
def apply_post_fingerprint_params(CP: structs.CarParams, candidate, fingerprint, car_fw) -> None:
|
||||
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin):
|
||||
gt_line_testing_ground = testing_ground.use(KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID)
|
||||
if kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, gt_line_testing_ground):
|
||||
apply_kia_ev6_gt_line_longitudinal_params(CP)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1043,6 +1043,24 @@ class TestHyundaiFingerprint:
|
||||
assert reset_state.accel_last == pytest.approx(0.0)
|
||||
assert reset_state.long_control_state_last == LongCtrlState.off
|
||||
|
||||
def test_kia_ev6_gt_line_testing_ground_longitudinal_params(self, monkeypatch):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
CP.carVin = "00000000000000000"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"opendbc.car.hyundai.interface.testing_ground",
|
||||
SimpleNamespace(use=lambda slot_id: slot_id == "5"),
|
||||
)
|
||||
CarInterface.apply_post_fingerprint_params(CP, CAR.KIA_EV6, gen_empty_fingerprint(), [])
|
||||
|
||||
assert CP.startAccel == pytest.approx(1.4)
|
||||
assert CP.vEgoStarting == pytest.approx(0.5)
|
||||
assert CP.longitudinalActuatorDelay == pytest.approx(0.35)
|
||||
|
||||
assert kia_ev6_gt_line_longitudinal_tuning(CP.carFingerprint, CP.carVin, testing_ground_active=True)
|
||||
assert not kia_ev6_gt_line_longitudinal_tuning(CAR.KIA_EV6_2025, CP.carVin, testing_ground_active=True)
|
||||
|
||||
def test_kia_ev6_non_gt_line_keeps_family_longitudinal_params(self):
|
||||
toggles = get_test_toggles()
|
||||
CP = CarInterface.get_params(CAR.KIA_EV6, gen_empty_fingerprint(), [], True, False, False, toggles)
|
||||
|
||||
@@ -975,6 +975,7 @@ CAN_CANFD_BLENDED_HDA2_LONGITUDINAL_CAR = frozenset({
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES = frozenset({
|
||||
"C4DLC",
|
||||
})
|
||||
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID = "5"
|
||||
|
||||
|
||||
ALT_BUS_LDA_BUTTON_CARS = frozenset()
|
||||
@@ -985,9 +986,9 @@ def hyundai_cancel_button_enables_cruise(car_fingerprint) -> bool:
|
||||
return car_fingerprint in CANCEL_BUTTON_ENABLE_CARS
|
||||
|
||||
|
||||
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str) -> bool:
|
||||
return car_fingerprint == CAR.KIA_EV6 and isinstance(vin, str) and \
|
||||
len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
|
||||
def kia_ev6_gt_line_longitudinal_tuning(car_fingerprint, vin: str, testing_ground_active: bool = False) -> bool:
|
||||
vin_match = isinstance(vin, str) and len(vin) == 17 and vin[3:8] in KIA_EV6_GT_LINE_LONG_TUNING_VDS_PREFIXES
|
||||
return car_fingerprint == CAR.KIA_EV6 and (vin_match or testing_ground_active)
|
||||
|
||||
|
||||
def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]:
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
fonts/*.fnt
|
||||
fonts/*.png
|
||||
!fonts/como-heavy.fnt
|
||||
!fonts/como-heavy.png
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
info face="como-heavy" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
|
||||
common lineHeight=200 base=200 scaleW=1024 scaleH=512 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
|
||||
page id=0 file="como-heavy.png"
|
||||
chars count=13
|
||||
char id=32 x=6 y=6 width=39 height=200 xoffset=0 yoffset=0 xadvance=39 page=0 chnl=15
|
||||
char id=63 x=57 y=6 width=81 height=115 xoffset=0 yoffset=47 xadvance=83 page=0 chnl=15
|
||||
char id=80 x=150 y=6 width=100 height=113 xoffset=8 yoffset=49 xadvance=110 page=0 chnl=15
|
||||
char id=83 x=262 y=6 width=101 height=116 xoffset=4 yoffset=47 xadvance=108 page=0 chnl=15
|
||||
char id=97 x=375 y=6 width=88 height=85 xoffset=5 yoffset=78 xadvance=100 page=0 chnl=15
|
||||
char id=105 x=475 y=6 width=31 height=121 xoffset=6 yoffset=41 xadvance=43 page=0 chnl=15
|
||||
char id=108 x=518 y=6 width=29 height=119 xoffset=7 yoffset=43 xadvance=43 page=0 chnl=15
|
||||
char id=111 x=559 y=6 width=88 height=85 xoffset=5 yoffset=78 xadvance=98 page=0 chnl=15
|
||||
char id=112 x=659 y=6 width=88 height=115 xoffset=7 yoffset=78 xadvance=100 page=0 chnl=15
|
||||
char id=114 x=759 y=6 width=63 height=83 xoffset=7 yoffset=79 xadvance=69 page=0 chnl=15
|
||||
char id=115 x=834 y=6 width=77 height=85 xoffset=4 yoffset=78 xadvance=84 page=0 chnl=15
|
||||
char id=116 x=923 y=6 width=63 height=111 xoffset=6 yoffset=52 xadvance=70 page=0 chnl=15
|
||||
char id=122 x=6 y=218 width=79 height=81 xoffset=3 yoffset=80 xadvance=85 page=0 chnl=15
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -12,6 +12,7 @@ LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json"
|
||||
GLYPH_PADDING = 6
|
||||
EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥²⚠ⓘ"
|
||||
UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"}
|
||||
BRAND_FONT_CHARS = " StarPilotstarpilot?z"
|
||||
|
||||
|
||||
def _languages():
|
||||
@@ -127,7 +128,10 @@ def main():
|
||||
for font in fonts:
|
||||
if "emoji" in font.name.lower():
|
||||
continue
|
||||
glyphs = unifont_cp if font.stem.lower().startswith("unifont") else base_cp
|
||||
if font.name == "como-heavy.otf":
|
||||
glyphs = tuple(sorted({ord(c) for c in BRAND_FONT_CHARS}))
|
||||
else:
|
||||
glyphs = unifont_cp if font.stem.lower().startswith("unifont") else base_cp
|
||||
_process_font(font, glyphs)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ TOYOTA_RAV4_TSS2_RADAR_FOLLOW_MAX_DISTANCE = 100.0
|
||||
TOYOTA_RAV4_TSS2_RADAR_FOLLOW_DISTANCE_TIME = 4.5
|
||||
TOYOTA_RAV4_TSS2_RADAR_FOLLOW_DISTANCE_OFFSET = 32.0
|
||||
TOYOTA_RAV4_TSS2_RADAR_FOLLOW_MAX_LATERAL_OFFSET = 1.75
|
||||
TOYOTA_RAV4_TSS2_FAR_FOLLOW_BRAKE_SLEW_RATE = 2.5
|
||||
TOYOTA_RAV4_TSS2_FAR_FOLLOW_RELEASE_SLEW_RATE = 1.75
|
||||
TOYOTA_CAMRY_TSS2_FORCE_STOP_HANDOFF_M = 4.5
|
||||
# The Camry's force-stop path otherwise consumes the model endpoint before the
|
||||
# normal MPC stop-distance margin can be applied. Keep it within the forward
|
||||
@@ -132,6 +134,11 @@ def get_far_follow_output_slew_rates(CP):
|
||||
HONDA_HRV_3G_FAR_FOLLOW_BRAKE_SLEW_RATE,
|
||||
HONDA_HRV_3G_FAR_FOLLOW_RELEASE_SLEW_RATE,
|
||||
)
|
||||
if is_toyota_rav4_tss2_post_departure_tune(CP):
|
||||
return (
|
||||
TOYOTA_RAV4_TSS2_FAR_FOLLOW_BRAKE_SLEW_RATE,
|
||||
TOYOTA_RAV4_TSS2_FAR_FOLLOW_RELEASE_SLEW_RATE,
|
||||
)
|
||||
return 0.0, 0.0
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import (
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import (
|
||||
allow_radar_standstill_gap_settle,
|
||||
get_far_follow_output_slew_rates,
|
||||
get_follow_prebrake_min_headway,
|
||||
get_toyota_rav4_tss2_early_lead_cap,
|
||||
get_toyota_sienna_post_departure_restop_cap,
|
||||
@@ -239,6 +240,28 @@ def test_non_hrv_has_no_vehicle_far_follow_output_slew():
|
||||
assert target == pytest.approx(-1.0)
|
||||
|
||||
|
||||
def test_rav4_far_follow_output_slew_damps_vision_lead_chatter():
|
||||
v_ego = 24.0
|
||||
CP = ToyotaCarInterface.get_non_essential_params(TOYOTA_CAR.TOYOTA_RAV4_TSS2_2023)
|
||||
planner = LongitudinalPlanner(CP, init_v=v_ego)
|
||||
planner.lead_one = make_lead(status=True, d_rel=58.0, v_lead=20.0, model_prob=0.99)
|
||||
planner.lead_two = make_lead(status=False)
|
||||
|
||||
brake_rate, release_rate = get_far_follow_output_slew_rates(CP)
|
||||
assert brake_rate > 0.0
|
||||
assert release_rate > 0.0
|
||||
|
||||
initial = planner.get_vehicle_far_follow_slew_target(
|
||||
v_ego, prev_target=0.0, target=-0.6, output_should_stop=False, panic_bypass=False,
|
||||
)
|
||||
smoothed = planner.get_vehicle_far_follow_slew_target(
|
||||
v_ego, prev_target=initial, target=0.4, output_should_stop=False, panic_bypass=False,
|
||||
)
|
||||
|
||||
assert initial == pytest.approx(-0.6)
|
||||
assert smoothed == pytest.approx(initial + release_rate * planner.dt)
|
||||
|
||||
|
||||
def test_depart_release_hold_rejects_nearby_stopped_lead_conflict():
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
planner = LongitudinalPlanner(CP)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@@ -8,6 +9,7 @@ from openpilot.starpilot.common.accel_profile import A_CRUISE_MAX_BP_CUSTOM, ACC
|
||||
from openpilot.starpilot.controls.lib.starpilot_acceleration import (
|
||||
A_CRUISE_MIN_ECO,
|
||||
A_CRUISE_MIN_TRAFFIC,
|
||||
PULSE_GLIDE_COAST_MIN_ACCEL,
|
||||
StarPilotAcceleration,
|
||||
get_max_accel_eco,
|
||||
get_max_accel_standard,
|
||||
@@ -55,9 +57,11 @@ def make_lead(status=False, d_rel=150.0, v_lead=0.0, a_lead_k=0.0):
|
||||
|
||||
|
||||
def make_sm(*, set_speed_kph=100.0, lead_one=None, lead_two=None, standstill=False, force_decel=False,
|
||||
eco_gear=False, sport_gear=False, force_coast=False, pulse_and_glide=False, traffic_mode=False, v_ego_cluster=0.0):
|
||||
eco_gear=False, sport_gear=False, force_coast=False, pulse_and_glide=False, traffic_mode=False,
|
||||
v_ego_cluster=0.0, pitch=0.0):
|
||||
return {
|
||||
"carState": SimpleNamespace(vCruise=set_speed_kph, standstill=standstill, vEgoCluster=v_ego_cluster),
|
||||
"carControl": SimpleNamespace(orientationNED=[0.0, pitch, 0.0]),
|
||||
"controlsState": SimpleNamespace(forceDecel=force_decel),
|
||||
"radarState": SimpleNamespace(
|
||||
leadOne=lead_one or make_lead(),
|
||||
@@ -222,7 +226,7 @@ def test_pulse_and_glide_coasts_at_set_speed_then_resumes_below_delta():
|
||||
accel.update(set_speed, make_sm(set_speed_kph=100.0, pulse_and_glide=True), toggles)
|
||||
assert accel.pulse_glide_coasting is True
|
||||
assert accel.pulse_glide_target == pytest.approx(set_speed - delta)
|
||||
assert accel.min_accel == pytest.approx(A_CRUISE_MIN_ECO)
|
||||
assert accel.min_accel == pytest.approx(PULSE_GLIDE_COAST_MIN_ACCEL)
|
||||
|
||||
accel.update((90.0 * CV.KPH_TO_MS) - 0.05, make_sm(set_speed_kph=100.0, pulse_and_glide=True), toggles)
|
||||
assert accel.pulse_glide_coasting is False
|
||||
@@ -232,7 +236,23 @@ def test_pulse_and_glide_coasts_at_set_speed_then_resumes_below_delta():
|
||||
accel.update(99.8 * CV.KPH_TO_MS, make_sm(set_speed_kph=100.0, pulse_and_glide=True), toggles)
|
||||
assert accel.pulse_glide_coasting is True
|
||||
assert accel.pulse_glide_target == pytest.approx(set_speed - delta)
|
||||
assert accel.min_accel == pytest.approx(A_CRUISE_MIN_ECO)
|
||||
assert accel.min_accel == pytest.approx(PULSE_GLIDE_COAST_MIN_ACCEL)
|
||||
|
||||
|
||||
def test_pulse_and_glide_pauses_on_steep_grade_then_resumes():
|
||||
set_speed = 100.0 * CV.KPH_TO_MS
|
||||
delta = 10.0 * CV.KPH_TO_MS
|
||||
accel = StarPilotAcceleration(FakePlanner(v_cruise=set_speed))
|
||||
toggles = make_toggles(pulse_glide_speed_delta=delta)
|
||||
|
||||
accel.update(set_speed, make_sm(set_speed_kph=100.0, pulse_and_glide=True, pitch=math.radians(4.0)), toggles)
|
||||
assert accel.pulse_glide_coasting is False
|
||||
assert accel.pulse_glide_hill_paused is True
|
||||
|
||||
accel.update(set_speed, make_sm(set_speed_kph=100.0, pulse_and_glide=True, pitch=math.radians(2.0)), toggles)
|
||||
assert accel.pulse_glide_coasting is True
|
||||
assert accel.pulse_glide_hill_paused is False
|
||||
assert accel.min_accel == pytest.approx(PULSE_GLIDE_COAST_MIN_ACCEL)
|
||||
|
||||
|
||||
def test_pulse_and_glide_is_inert_when_disabled():
|
||||
|
||||
@@ -181,12 +181,31 @@ class HomeLayout(Widget):
|
||||
|
||||
version_rect = rl.Rectangle(self.header_rect.x + self.header_rect.width - version_text_width, self.header_rect.y,
|
||||
version_text_width, self.header_rect.height)
|
||||
brand_text = "StarPilot"
|
||||
detail_text = self._version_text.removeprefix(brand_text)
|
||||
brand_font = gui_app.font(FontWeight.BRAND)
|
||||
version_font_size = 48
|
||||
version_text_size = measure_text_cached(font, self._version_text, version_font_size)
|
||||
if version_text_size.x > version_rect.width:
|
||||
version_font_size = max(32, int(version_font_size * version_rect.width / version_text_size.x))
|
||||
gui_label(version_rect, self._version_text, version_font_size, rl.WHITE, font_weight=FontWeight.MEDIUM,
|
||||
alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT)
|
||||
|
||||
def _measure_header(font_size: int) -> tuple[rl.Vector2, rl.Vector2]:
|
||||
return (measure_text_cached(brand_font, brand_text, font_size + 2),
|
||||
measure_text_cached(font, detail_text, font_size))
|
||||
|
||||
brand_size, detail_size = _measure_header(version_font_size)
|
||||
total_width = brand_size.x + detail_size.x
|
||||
if total_width > version_rect.width:
|
||||
version_font_size = max(32, int(version_font_size * version_rect.width / total_width))
|
||||
brand_size, detail_size = _measure_header(version_font_size)
|
||||
total_width = brand_size.x + detail_size.x
|
||||
|
||||
rendered_width = min(total_width, version_rect.width)
|
||||
text_x = version_rect.x + version_rect.width - rendered_width
|
||||
brand_rect = rl.Rectangle(text_x, version_rect.y, min(brand_size.x, rendered_width), version_rect.height)
|
||||
gui_label(brand_rect, brand_text, version_font_size + 2, rl.WHITE, font_weight=FontWeight.BRAND, elide_right=False)
|
||||
|
||||
detail_width = max(0.0, rendered_width - brand_rect.width)
|
||||
if detail_text and detail_width > 0:
|
||||
detail_rect = rl.Rectangle(brand_rect.x + brand_rect.width, version_rect.y, detail_width, version_rect.height)
|
||||
gui_label(detail_rect, detail_text, version_font_size, rl.WHITE, font_weight=FontWeight.MEDIUM)
|
||||
|
||||
def _render_home_content(self):
|
||||
self._render_left_column()
|
||||
|
||||
@@ -173,7 +173,7 @@ class MiciHomeLayout(Widget):
|
||||
self._mic_icon,
|
||||
], spacing=18)
|
||||
|
||||
self._openpilot_label = UnifiedLabel("starpilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False)
|
||||
self._openpilot_label = UnifiedLabel("StarPilot", font_size=96, font_weight=FontWeight.BRAND, max_width=480, wrap_text=False)
|
||||
self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||
self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||
self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.lib.text_measure import draw_text_with_shadow, measure_text_cached
|
||||
|
||||
|
||||
PULSE_COLOR = rl.Color(52, 190, 112, 255)
|
||||
GLIDE_COLOR = rl.Color(65, 155, 235, 255)
|
||||
|
||||
|
||||
def render_pulse_glide(rect: rl.Rectangle, coasting: bool) -> None:
|
||||
"""Render the developer-only P&G phase badge beside the standard HUD badges."""
|
||||
border = GLIDE_COLOR if coasting else PULSE_COLOR
|
||||
label = "GLIDE" if coasting else "PULSE"
|
||||
font = gui_app.font(FontWeight.BOLD)
|
||||
font_size = 27
|
||||
text_size = measure_text_cached(font, label, font_size)
|
||||
|
||||
rl.draw_rectangle_rounded(rect, 0.3, 10, rl.Color(0, 0, 0, 166))
|
||||
rl.draw_rectangle_rounded_lines_ex(rect, 0.3, 10, 4, border)
|
||||
draw_text_with_shadow(
|
||||
font,
|
||||
label,
|
||||
rl.Vector2(rect.x + (rect.width - text_size.x) / 2, rect.y + (rect.height - text_size.y) / 2),
|
||||
font_size,
|
||||
rl.WHITE,
|
||||
)
|
||||
@@ -14,6 +14,7 @@ from openpilot.selfdrive.ui.onroad.starpilot.widgets import (
|
||||
)
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.stopping_point import render_stopping_point
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.pause_indicators import render_lateral_paused, render_longitudinal_paused
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.pulse_glide import render_pulse_glide
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.pip_sidecam import PipSideCamera
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.favorite_radial_menu import FavoriteRadialMenu
|
||||
from openpilot.selfdrive.ui.onroad.starpilot.weather_icon import render_weather_icon
|
||||
@@ -342,6 +343,7 @@ class StarPilotOnroadView(AugmentedRoadView):
|
||||
|
||||
# Check pause/CEM states
|
||||
starpilot_car_state = ui_state.sm["starpilotCarState"] if ui_state.sm.valid.get("starpilotCarState", False) else None
|
||||
plan = ui_state.sm["starpilotPlan"] if ui_state.sm.valid.get("starpilotPlan", False) else None
|
||||
lateral_paused = starpilot_car_state.pauseLateral if starpilot_car_state else False
|
||||
longitudinal_paused = (starpilot_car_state.pauseLongitudinal or starpilot_car_state.forceCoast) if starpilot_car_state else False
|
||||
|
||||
@@ -352,6 +354,8 @@ class StarPilotOnroadView(AugmentedRoadView):
|
||||
active_badges.append("lateral_paused")
|
||||
if longitudinal_paused:
|
||||
active_badges.append("longitudinal_paused")
|
||||
if starpilot_car_state and starpilot_car_state.pulseAndGlide:
|
||||
active_badges.append("pulse_glide")
|
||||
|
||||
# Dimensions
|
||||
badge_w = 120
|
||||
@@ -377,9 +381,11 @@ class StarPilotOnroadView(AugmentedRoadView):
|
||||
render_lateral_paused(badge_rect)
|
||||
elif badge == "longitudinal_paused":
|
||||
render_longitudinal_paused(badge_rect)
|
||||
elif badge == "pulse_glide":
|
||||
pulse_glide_coasting = bool(getattr(plan, "pulseGlideCoasting", False)) if plan else False
|
||||
render_pulse_glide(badge_rect, pulse_glide_coasting)
|
||||
|
||||
# 2. Render Weather (on the opposite side of DM icon)
|
||||
plan = ui_state.sm["starpilotPlan"] if ui_state.sm.valid.get("starpilotPlan", False) else None
|
||||
if plan and plan.weatherId != 0:
|
||||
weather_w = 120
|
||||
weather_h = 120
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import dataclasses
|
||||
import json
|
||||
import requests
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -34,6 +35,10 @@ from openpilot.starpilot.common.starpilot_variables import (
|
||||
StarPilotVariables, get_starpilot_toggles
|
||||
)
|
||||
|
||||
BOOT_LOGO_JPEG_PATH = Path("/usr/comma/bg.jpg")
|
||||
BOOT_LOGO_PNG_PATH = Path("/usr/comma/bg.png")
|
||||
BOOT_LOGO_MAGIC_PATH = Path("/usr/comma/magic.py")
|
||||
|
||||
|
||||
def seed_desktop_theme_assets():
|
||||
params = Params()
|
||||
@@ -163,8 +168,6 @@ def update_boot_logo(starpilot=False, stock=False, selected_logo=None):
|
||||
if HARDWARE.get_device_type() == "pc":
|
||||
return
|
||||
|
||||
boot_logo_location = Path("/usr/comma/bg.jpg")
|
||||
|
||||
if starpilot:
|
||||
target_logo = Path(BASEDIR) / "starpilot/assets/other_images/starpilot_boot_logo.jpg"
|
||||
if selected_logo:
|
||||
@@ -184,30 +187,57 @@ def update_boot_logo(starpilot=False, stock=False, selected_logo=None):
|
||||
print(f"Error: Target logo file not found at {target_logo}")
|
||||
return
|
||||
|
||||
source_logo = target_logo
|
||||
staged_logo = Path("/tmp/starpilot_boot_logo.jpg")
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
with Image.open(target_logo) as img:
|
||||
# weston.service always writes a JPEG copy of /usr/comma/bg.jpg; make sure
|
||||
# the source image is already RGB JPEG to avoid startup failure on RGBA assets.
|
||||
if img.format != "JPEG" or img.mode != "RGB":
|
||||
img.convert("RGB").save(staged_logo, format="JPEG", quality=95)
|
||||
source_logo = staged_logo
|
||||
with tempfile.TemporaryDirectory(prefix="starpilot_boot_logo_") as staging_dir:
|
||||
staging_path = Path(staging_dir)
|
||||
staged_jpeg = staging_path / "bg.jpg"
|
||||
staged_png = staging_path / "bg.png"
|
||||
|
||||
with Image.open(target_logo) as img:
|
||||
normalized_logo = img.convert("RGB")
|
||||
if normalized_logo.width >= normalized_logo.height:
|
||||
landscape_logo = normalized_logo
|
||||
weston_logo = normalized_logo.transpose(Image.Transpose.ROTATE_270)
|
||||
else:
|
||||
weston_logo = normalized_logo
|
||||
landscape_logo = normalized_logo.transpose(Image.Transpose.ROTATE_90)
|
||||
|
||||
magic_uses_jpeg = False
|
||||
try:
|
||||
magic_uses_jpeg = BOOT_LOGO_JPEG_PATH.as_posix() in BOOT_LOGO_MAGIC_PATH.read_text()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
(landscape_logo if magic_uses_jpeg else weston_logo).save(staged_jpeg, format="JPEG", quality=95)
|
||||
landscape_logo.save(staged_png, format="PNG")
|
||||
|
||||
logo_variants = [(staged_jpeg, BOOT_LOGO_JPEG_PATH)]
|
||||
if BOOT_LOGO_PNG_PATH.is_file():
|
||||
logo_variants.append((staged_png, BOOT_LOGO_PNG_PATH))
|
||||
|
||||
pending_updates = [
|
||||
(source, destination)
|
||||
for source, destination in logo_variants
|
||||
if not destination.is_file() or destination.read_bytes() != source.read_bytes()
|
||||
]
|
||||
if not pending_updates:
|
||||
return
|
||||
|
||||
mount_options = run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/"], "Successfully retrieved mount options", "Failed to retrieve mount options")
|
||||
if mount_options is None:
|
||||
return
|
||||
if run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount /") is None:
|
||||
return
|
||||
|
||||
try:
|
||||
for source, destination in pending_updates:
|
||||
run_cmd(["sudo", "cp", source, destination], f"Successfully replaced boot logo at {destination}", f"Failed to replace boot logo at {destination}")
|
||||
finally:
|
||||
run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options")
|
||||
except Exception as error:
|
||||
print(f"Error normalizing boot logo {target_logo}: {error}")
|
||||
if target_logo.suffix.lower() not in {".jpg", ".jpeg"}:
|
||||
print("Skipping boot logo update to keep weston startup stable.")
|
||||
return
|
||||
|
||||
current_logo = boot_logo_location.read_bytes() if boot_logo_location.is_file() else b""
|
||||
desired_logo = source_logo.read_bytes()
|
||||
if current_logo != desired_logo:
|
||||
mount_options = run_cmd(["findmnt", "-n", "-o", "OPTIONS", "/"], "Successfully retrieved mount options", "Failed to retrieve mount options")
|
||||
run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount /")
|
||||
run_cmd(["sudo", "cp", source_logo, boot_logo_location], "Successfully replaced boot logo", "Failed to replace boot logo")
|
||||
run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options")
|
||||
|
||||
|
||||
MAPS_DOWNLOAD_PROGRESS_PARAM = "MapsDownloadProgress"
|
||||
|
||||
@@ -63,9 +63,10 @@ TESTING_GROUNDS_SLOT_DEFINITIONS = (
|
||||
},
|
||||
{
|
||||
"id": TESTING_GROUND_5,
|
||||
"name": "Unused",
|
||||
"description": "Unused slot.",
|
||||
"name": "EV6 GT-Line Long",
|
||||
"description": "Kia EV6 GT-Line longitudinal tuning sandbox.",
|
||||
"aLabel": "A - Installed tune",
|
||||
"bLabel": "B - EV6 GT-Line long tune",
|
||||
},
|
||||
{
|
||||
"id": TESTING_GROUND_6,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from openpilot.starpilot.common import connect_server as cs
|
||||
from openpilot.starpilot.common import starpilot_functions as sf
|
||||
|
||||
@@ -27,6 +32,157 @@ class FakeThreadManager:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("extension,image_format", [("jpg", "JPEG"), ("png", "PNG")])
|
||||
def test_update_boot_logo_writes_agnos_jpeg_and_png(monkeypatch, tmp_path, extension, image_format):
|
||||
themes_path = tmp_path / "themes"
|
||||
custom_logo = themes_path / "bootlogos" / f"custom.{extension}"
|
||||
custom_logo.parent.mkdir(parents=True)
|
||||
Image.new("RGBA" if image_format == "PNG" else "RGB", (24, 12), (12, 34, 56, 255)).save(custom_logo, format=image_format)
|
||||
|
||||
jpeg_destination = tmp_path / "usr" / "comma" / "bg.jpg"
|
||||
png_destination = tmp_path / "usr" / "comma" / "bg.png"
|
||||
jpeg_destination.parent.mkdir(parents=True)
|
||||
jpeg_destination.write_bytes(b"old jpeg")
|
||||
png_destination.write_bytes(b"old png")
|
||||
|
||||
commands = []
|
||||
|
||||
def fake_run_cmd(command, *_args, **_kwargs):
|
||||
commands.append(command)
|
||||
if command[0] == "findmnt":
|
||||
return "ro,relatime"
|
||||
if command[:2] == ["sudo", "cp"]:
|
||||
shutil.copy2(command[2], command[3])
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(sf.HARDWARE, "get_device_type", lambda: "mici")
|
||||
monkeypatch.setattr(sf, "THEME_SAVE_PATH", themes_path)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_JPEG_PATH", jpeg_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_PNG_PATH", png_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_MAGIC_PATH", tmp_path / "usr" / "comma" / "magic.py")
|
||||
monkeypatch.setattr(sf, "run_cmd", fake_run_cmd)
|
||||
|
||||
sf.update_boot_logo(starpilot=True, selected_logo="custom")
|
||||
|
||||
with Image.open(jpeg_destination) as jpeg_logo:
|
||||
assert jpeg_logo.format == "JPEG"
|
||||
assert jpeg_logo.mode == "RGB"
|
||||
assert jpeg_logo.size == (12, 24)
|
||||
with Image.open(png_destination) as png_logo:
|
||||
assert png_logo.format == "PNG"
|
||||
assert png_logo.mode == "RGB"
|
||||
assert png_logo.size == (24, 12)
|
||||
|
||||
assert [command for command in commands if command[:2] == ["sudo", "mount"]] == [
|
||||
["sudo", "mount", "-o", "remount,rw", "/"],
|
||||
["sudo", "mount", "-o", "remount,ro,relatime", "/"],
|
||||
]
|
||||
assert len([command for command in commands if command[:2] == ["sudo", "cp"]]) == 2
|
||||
|
||||
|
||||
def test_update_boot_logo_does_not_create_png_on_legacy_agnos(monkeypatch, tmp_path):
|
||||
themes_path = tmp_path / "themes"
|
||||
custom_logo = themes_path / "bootlogos" / "custom.png"
|
||||
custom_logo.parent.mkdir(parents=True)
|
||||
Image.new("RGB", (24, 12), (12, 34, 56)).save(custom_logo, format="PNG")
|
||||
|
||||
jpeg_destination = tmp_path / "usr" / "comma" / "bg.jpg"
|
||||
png_destination = tmp_path / "usr" / "comma" / "bg.png"
|
||||
jpeg_destination.parent.mkdir(parents=True)
|
||||
jpeg_destination.write_bytes(b"old jpeg")
|
||||
|
||||
def fake_run_cmd(command, *_args, **_kwargs):
|
||||
if command[0] == "findmnt":
|
||||
return "ro,relatime"
|
||||
if command[:2] == ["sudo", "cp"]:
|
||||
shutil.copy2(command[2], command[3])
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(sf.HARDWARE, "get_device_type", lambda: "tici")
|
||||
monkeypatch.setattr(sf, "THEME_SAVE_PATH", themes_path)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_JPEG_PATH", jpeg_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_PNG_PATH", png_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_MAGIC_PATH", tmp_path / "usr" / "comma" / "magic.py")
|
||||
monkeypatch.setattr(sf, "run_cmd", fake_run_cmd)
|
||||
|
||||
sf.update_boot_logo(starpilot=True, selected_logo="custom")
|
||||
|
||||
with Image.open(jpeg_destination) as jpeg_logo:
|
||||
assert jpeg_logo.format == "JPEG"
|
||||
assert jpeg_logo.size == (12, 24)
|
||||
assert not png_destination.exists()
|
||||
|
||||
|
||||
def test_update_boot_logo_rotates_legacy_stock_for_raylib(monkeypatch, tmp_path):
|
||||
stock_logo = tmp_path / "starpilot" / "assets" / "other_images" / "stock_bg.jpg"
|
||||
stock_logo.parent.mkdir(parents=True)
|
||||
Image.new("RGB", (12, 24), (12, 34, 56)).save(stock_logo, format="JPEG")
|
||||
|
||||
comma_path = tmp_path / "usr" / "comma"
|
||||
jpeg_destination = comma_path / "bg.jpg"
|
||||
png_destination = comma_path / "bg.png"
|
||||
comma_path.mkdir(parents=True)
|
||||
jpeg_destination.write_bytes(b"old jpeg")
|
||||
png_destination.write_bytes(b"old png")
|
||||
|
||||
def fake_run_cmd(command, *_args, **_kwargs):
|
||||
if command[0] == "findmnt":
|
||||
return "ro,relatime"
|
||||
if command[:2] == ["sudo", "cp"]:
|
||||
shutil.copy2(command[2], command[3])
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(sf.HARDWARE, "get_device_type", lambda: "mici")
|
||||
monkeypatch.setattr(sf, "BASEDIR", str(tmp_path))
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_JPEG_PATH", jpeg_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_PNG_PATH", png_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_MAGIC_PATH", comma_path / "magic.py")
|
||||
monkeypatch.setattr(sf, "run_cmd", fake_run_cmd)
|
||||
|
||||
sf.update_boot_logo(stock=True)
|
||||
|
||||
with Image.open(jpeg_destination) as jpeg_logo:
|
||||
assert jpeg_logo.size == (12, 24)
|
||||
with Image.open(png_destination) as png_logo:
|
||||
assert png_logo.size == (24, 12)
|
||||
|
||||
|
||||
def test_update_boot_logo_uses_landscape_jpeg_for_current_magic(monkeypatch, tmp_path):
|
||||
themes_path = tmp_path / "themes"
|
||||
custom_logo = themes_path / "bootlogos" / "custom.png"
|
||||
custom_logo.parent.mkdir(parents=True)
|
||||
Image.new("RGB", (12, 24), (12, 34, 56)).save(custom_logo, format="PNG")
|
||||
|
||||
comma_path = tmp_path / "usr" / "comma"
|
||||
jpeg_destination = comma_path / "bg.jpg"
|
||||
png_destination = comma_path / "bg.png"
|
||||
magic_path = comma_path / "magic.py"
|
||||
comma_path.mkdir(parents=True)
|
||||
jpeg_destination.write_bytes(b"old jpeg")
|
||||
magic_path.write_text(f'BACKGROUND = "{jpeg_destination.as_posix()}"\n')
|
||||
|
||||
def fake_run_cmd(command, *_args, **_kwargs):
|
||||
if command[0] == "findmnt":
|
||||
return "ro,relatime"
|
||||
if command[:2] == ["sudo", "cp"]:
|
||||
shutil.copy2(command[2], command[3])
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr(sf.HARDWARE, "get_device_type", lambda: "mici")
|
||||
monkeypatch.setattr(sf, "THEME_SAVE_PATH", themes_path)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_JPEG_PATH", jpeg_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_PNG_PATH", png_destination)
|
||||
monkeypatch.setattr(sf, "BOOT_LOGO_MAGIC_PATH", magic_path)
|
||||
monkeypatch.setattr(sf, "run_cmd", fake_run_cmd)
|
||||
|
||||
sf.update_boot_logo(starpilot=True, selected_logo="custom")
|
||||
|
||||
with Image.open(jpeg_destination) as jpeg_logo:
|
||||
assert jpeg_logo.format == "JPEG"
|
||||
assert jpeg_logo.size == (24, 12)
|
||||
assert not png_destination.exists()
|
||||
|
||||
|
||||
def test_automatic_update_requests_guarded_reboot(monkeypatch):
|
||||
params = FakeParams({
|
||||
"UpdaterState": "idle",
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.starpilot.common import testing_grounds as tg
|
||||
|
||||
|
||||
@pytest.mark.parametrize("hidden_slot_id", [tg.TESTING_GROUND_5])
|
||||
def test_hidden_testing_ground_selection_is_migrated(tmp_path, monkeypatch, hidden_slot_id):
|
||||
def test_invalid_testing_ground_selection_is_migrated(tmp_path, monkeypatch):
|
||||
invalid_slot_id = "99"
|
||||
state_path = tmp_path / "slots.json"
|
||||
state_path.write_text(json.dumps({
|
||||
"schemaVersion": tg.TESTING_GROUNDS_SCHEMA_VERSION,
|
||||
"activeSlot": hidden_slot_id,
|
||||
"activeSlot": invalid_slot_id,
|
||||
"activeVariant": tg.TESTING_GROUND_TEST_VARIANT,
|
||||
}), encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(tg, "TESTING_GROUNDS_STATE_PATH", state_path)
|
||||
monkeypatch.setattr(tg, "_CACHE_LAST_REFRESH", 0.0)
|
||||
monkeypatch.setattr(tg, "_CACHE_LAST_MTIME_NS", -1)
|
||||
monkeypatch.setattr(tg, "_VISIBLE_TESTING_GROUND_IDS", tuple(slot_id for slot_id in tg.TESTING_GROUND_IDS if slot_id != hidden_slot_id))
|
||||
monkeypatch.setattr(tg, "_VISIBLE_TESTING_GROUND_IDS", tuple(tg.TESTING_GROUND_IDS))
|
||||
monkeypatch.setattr(tg, "_DEFAULT_ACTIVE_SLOT", tg.TESTING_GROUND_1)
|
||||
monkeypatch.setattr(tg, "_CACHE_ACTIVE_SLOT", tg._DEFAULT_ACTIVE_SLOT)
|
||||
monkeypatch.setattr(tg, "_CACHE_ACTIVE_VARIANT", tg.DEFAULT_TESTING_GROUND_VARIANT)
|
||||
@@ -32,11 +30,11 @@ def test_hidden_testing_ground_selection_is_migrated(tmp_path, monkeypatch, hidd
|
||||
assert payload["activeVariant"] == tg.DEFAULT_TESTING_GROUND_VARIANT
|
||||
|
||||
|
||||
def test_hidden_slot_invalid_variant_is_migrated_off_slot(tmp_path, monkeypatch):
|
||||
def test_invalid_slot_variant_is_migrated_off_slot(tmp_path, monkeypatch):
|
||||
state_path = tmp_path / "slots.json"
|
||||
state_path.write_text(json.dumps({
|
||||
"schemaVersion": tg.TESTING_GROUNDS_SCHEMA_VERSION,
|
||||
"activeSlot": tg.TESTING_GROUND_5,
|
||||
"activeSlot": "99",
|
||||
"activeVariant": "C",
|
||||
}), encoding="utf-8")
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
@@ -79,6 +81,9 @@ RELEVANT_LEAD_MIN_BRAKE = -0.4
|
||||
PULSE_GLIDE_MIN_TARGET_SPEED = 5.0
|
||||
PULSE_GLIDE_MIN_LOWER_SPEED = 3.0
|
||||
PULSE_GLIDE_HYSTERESIS = 0.25
|
||||
PULSE_GLIDE_COAST_MIN_ACCEL = -0.03
|
||||
PULSE_GLIDE_HILL_ENTER_PITCH = math.radians(3.0)
|
||||
PULSE_GLIDE_HILL_EXIT_PITCH = math.radians(2.5)
|
||||
|
||||
# Drive mode -> profile mapping used by the map_acceleration / map_deceleration toggles.
|
||||
GEAR_STATE_PROFILES = {
|
||||
@@ -153,11 +158,37 @@ class StarPilotAcceleration:
|
||||
self.last_gear_state = "init"
|
||||
self.pulse_glide_coasting = False
|
||||
self.pulse_glide_target = None
|
||||
self.pulse_glide_hill_paused = False
|
||||
|
||||
def _update_pulse_glide_hill_pause(self, sm):
|
||||
try:
|
||||
orientation_ned = sm["carControl"].orientationNED
|
||||
if len(orientation_ned) < 2:
|
||||
return self.pulse_glide_hill_paused
|
||||
abs_pitch = abs(float(orientation_ned[1]))
|
||||
except (KeyError, IndexError, TypeError, ValueError, AttributeError):
|
||||
return self.pulse_glide_hill_paused
|
||||
|
||||
if not math.isfinite(abs_pitch):
|
||||
return self.pulse_glide_hill_paused
|
||||
|
||||
if self.pulse_glide_hill_paused:
|
||||
if abs_pitch <= PULSE_GLIDE_HILL_EXIT_PITCH:
|
||||
self.pulse_glide_hill_paused = False
|
||||
elif abs_pitch >= PULSE_GLIDE_HILL_ENTER_PITCH:
|
||||
self.pulse_glide_hill_paused = True
|
||||
|
||||
return self.pulse_glide_hill_paused
|
||||
|
||||
def _update_pulse_glide(self, v_ego, sm, starpilot_toggles):
|
||||
self.pulse_glide_target = None
|
||||
pulse_glide_enabled = bool(getattr(sm["starpilotCarState"], "pulseAndGlide", False))
|
||||
if not pulse_glide_enabled:
|
||||
self.pulse_glide_coasting = False
|
||||
self.pulse_glide_hill_paused = False
|
||||
return False
|
||||
|
||||
if self._update_pulse_glide_hill_pause(sm):
|
||||
self.pulse_glide_coasting = False
|
||||
return False
|
||||
|
||||
@@ -254,8 +285,10 @@ class StarPilotAcceleration:
|
||||
self.max_accel -= self.max_accel * self.starpilot_planner.starpilot_weather.reduce_acceleration
|
||||
|
||||
pulse_glide_coasting = self._update_pulse_glide(v_ego, sm, starpilot_toggles)
|
||||
if sm["starpilotCarState"].forceCoast or pulse_glide_coasting:
|
||||
if sm["starpilotCarState"].forceCoast:
|
||||
self.min_accel = A_CRUISE_MIN_ECO
|
||||
elif pulse_glide_coasting:
|
||||
self.min_accel = PULSE_GLIDE_COAST_MIN_ACCEL
|
||||
elif sm["starpilotCarState"].trafficModeEnabled:
|
||||
self.min_accel = A_CRUISE_MIN_TRAFFIC
|
||||
elif starpilot_toggles.map_deceleration and (eco_gear or sport_gear):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.chrysler.values import pacifica_hybrid_aol_requires_set_press
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, HyundaiFlags
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
@@ -276,6 +277,18 @@ class StarPilotCard:
|
||||
elif not self.distancePressed_previously:
|
||||
self.gap_counter = 0
|
||||
|
||||
distance_released = not starpilotCarState.distancePressed and self.distancePressed_previously
|
||||
has_distance_release = any(
|
||||
self._button_type_raw(be) == int(ButtonType.gapAdjustCruise) and not be.pressed
|
||||
for be in carState.buttonEvents
|
||||
)
|
||||
if getattr(self.CP, "carFingerprint", None) == HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024 and \
|
||||
distance_released and not has_distance_release:
|
||||
carState.buttonEvents = [
|
||||
*carState.buttonEvents,
|
||||
structs.CarState.ButtonEvent(pressed=False, type=ButtonType.gapAdjustCruise),
|
||||
]
|
||||
|
||||
self.distancePressed_previously = starpilotCarState.distancePressed
|
||||
|
||||
if not starpilotCarState.distancePressed and 1 <= self.gap_counter < self.long_press_threshold:
|
||||
|
||||
@@ -329,7 +329,11 @@ class StarPilotPlanner:
|
||||
starpilotPlan.cscTraining = self.starpilot_vcruise.csc.enable_training
|
||||
|
||||
starpilotPlan.desiredFollowDistance = int(self.starpilot_following.desired_follow_distance)
|
||||
starpilotPlan.disableThrottle = self.starpilot_following.disable_throttle
|
||||
starpilotPlan.disableThrottle = (
|
||||
self.starpilot_following.disable_throttle or
|
||||
self.starpilot_acceleration.pulse_glide_coasting
|
||||
)
|
||||
starpilotPlan.pulseGlideCoasting = self.starpilot_acceleration.pulse_glide_coasting
|
||||
starpilotPlan.trackingLead = self.tracking_lead
|
||||
|
||||
conditional_experimental_mode = False
|
||||
|
||||
@@ -219,6 +219,48 @@ def make_wrapped_button_event(button_type, pressed):
|
||||
return SimpleNamespace(type=SimpleNamespace(raw=int(button_type)), pressed=pressed)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("car_fingerprint", "expect_normalized_release"),
|
||||
(
|
||||
(spc.HYUNDAI_CAR.HYUNDAI_ELANTRA_HEV_2024, True),
|
||||
(spc.HYUNDAI_CAR.HYUNDAI_ELANTRA_2024, False),
|
||||
),
|
||||
)
|
||||
def test_distance_release_normalization_is_limited_to_reported_elantra_hybrid(
|
||||
monkeypatch, tmp_path, car_fingerprint, expect_normalized_release,
|
||||
):
|
||||
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", carFingerprint=car_fingerprint),
|
||||
SimpleNamespace(alternativeExperience=0),
|
||||
)
|
||||
toggles = make_toggles(
|
||||
experimental_mode_via_distance=False,
|
||||
bookmark_via_distance=False,
|
||||
force_coast_via_distance=False,
|
||||
pulse_and_glide_via_distance=False,
|
||||
pause_lateral_via_distance=False,
|
||||
pause_longitudinal_via_distance=False,
|
||||
switchback_mode_via_distance=False,
|
||||
)
|
||||
sm = make_sm()
|
||||
starpilot_car_state = SimpleNamespace(distancePressed=True)
|
||||
|
||||
card.update(make_car_state(), starpilot_car_state, sm, toggles)
|
||||
|
||||
starpilot_car_state.distancePressed = False
|
||||
car_state = make_car_state(button_events=[SimpleNamespace(type=spc.ButtonType.unknown, pressed=False)])
|
||||
card.update(car_state, starpilot_car_state, sm, toggles)
|
||||
|
||||
assert any(
|
||||
be.type == spc.ButtonType.gapAdjustCruise and not be.pressed
|
||||
for be in car_state.buttonEvents
|
||||
) is expect_normalized_release
|
||||
|
||||
|
||||
def test_honda_lkas_button_can_toggle_always_on_lateral(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(spc, "Params", FakeParams)
|
||||
monkeypatch.setattr(spc, "is_FrogsGoMoo", lambda: False)
|
||||
|
||||
@@ -155,6 +155,7 @@ class FontWeight(StrEnum):
|
||||
BOLD = "Inter-Bold.fnt"
|
||||
SEMI_BOLD = "Inter-SemiBold.fnt"
|
||||
UNIFONT = "unifont.fnt"
|
||||
BRAND = "como-heavy.fnt"
|
||||
|
||||
# Small UI fonts
|
||||
DISPLAY_REGULAR = "Inter-Regular.fnt"
|
||||
@@ -165,6 +166,11 @@ class FontWeight(StrEnum):
|
||||
def font_fallback(font: rl.Font) -> rl.Font:
|
||||
"""Fall back to unifont for languages that require it."""
|
||||
if multilang.requires_unifont():
|
||||
try:
|
||||
if font.texture.id == gui_app.font(FontWeight.BRAND).texture.id:
|
||||
return font
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
return gui_app.font(FontWeight.UNIFONT)
|
||||
return font
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from importlib.resources import as_file
|
||||
from types import SimpleNamespace
|
||||
|
||||
from openpilot.system.ui.lib import application
|
||||
|
||||
|
||||
@@ -35,3 +38,32 @@ def test_burn_in_shift_transitions_between_positions(monkeypatch):
|
||||
midpoint = app._burn_in_shift(109.0)
|
||||
assert midpoint == (-1.0, 0.0)
|
||||
assert app._burn_in_shift(110.0) == (-2.0, 0.0)
|
||||
|
||||
|
||||
def test_brand_font_assets_include_wordmark_glyphs():
|
||||
with as_file(application.FONT_DIR.joinpath("como-heavy.fnt")) as font_path:
|
||||
lines = font_path.read_text().splitlines()
|
||||
|
||||
glyphs = {}
|
||||
for line in lines:
|
||||
if not line.startswith("char id="):
|
||||
continue
|
||||
fields = dict(field.split("=", 1) for field in line.split() if "=" in field)
|
||||
glyphs[int(fields["id"])] = (int(fields["width"]), int(fields["height"]))
|
||||
|
||||
for char in set("StarPilot"):
|
||||
assert glyphs[ord(char)][0] > 0
|
||||
assert glyphs[ord(char)][1] > 0
|
||||
|
||||
|
||||
def test_brand_font_is_not_replaced_by_language_fallback(monkeypatch):
|
||||
brand_font = SimpleNamespace(texture=SimpleNamespace(id=1))
|
||||
unifont = SimpleNamespace(texture=SimpleNamespace(id=2))
|
||||
monkeypatch.setattr(application.multilang, "requires_unifont", lambda: True)
|
||||
monkeypatch.setattr(application.gui_app, "font", lambda weight: {
|
||||
application.FontWeight.BRAND: brand_font,
|
||||
application.FontWeight.UNIFONT: unifont,
|
||||
}[weight])
|
||||
|
||||
assert application.font_fallback(brand_font) is brand_font
|
||||
assert application.font_fallback(SimpleNamespace(texture=SimpleNamespace(id=3))) is unifont
|
||||
|
||||
Reference in New Issue
Block a user