mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-25 10:13:43 +08:00
Compare commits
4 Commits
Dom_New_Agnos
...
Dom
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a64493512 | |||
| 9c1d39b859 | |||
| ffab4a8321 | |||
| 71c1d79807 |
@@ -39,6 +39,7 @@ from openpilot.starpilot.controls.starpilot_card import StarPilotCard
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
OPENPILOT_LEAD_MIN_DISTANCE = 0.1
|
||||
REDNECK_DECREASE_LOOKAHEAD_POINTS = 10
|
||||
SLC_SOURCE_NONE = "None"
|
||||
EventName = log.OnroadEvent.EventName
|
||||
|
||||
# forward
|
||||
@@ -276,14 +277,19 @@ class Car:
|
||||
self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise
|
||||
)
|
||||
if not preap_software_cruise:
|
||||
speed_limit_confirmation_pending = is_speed_limit_confirmation_pending(self.sm['starpilotPlan'])
|
||||
starpilot_plan = self.sm['starpilotPlan']
|
||||
speed_limit_confirmation_pending = is_speed_limit_confirmation_pending(starpilot_plan)
|
||||
slc_target_with_offset = 0.0
|
||||
if self.starpilot_toggles.speed_limit_controller and starpilot_plan.slcSpeedLimitSource != SLC_SOURCE_NONE:
|
||||
slc_target_with_offset = starpilot_plan.slcSpeedLimit + starpilot_plan.slcSpeedLimitOffset
|
||||
self.v_cruise_helper.update_v_cruise(
|
||||
CS,
|
||||
self.sm['carControl'].enabled,
|
||||
self.is_metric,
|
||||
speed_limit_confirmation_pending,
|
||||
self.starpilot_toggles,
|
||||
FPCS,
|
||||
starpilot_car_state=FPCS,
|
||||
slc_target_with_offset=slc_target_with_offset,
|
||||
)
|
||||
else:
|
||||
preap_v_cruise_kph = float(CS.cruiseState.speed * CV.MS_TO_KPH)
|
||||
|
||||
+17
-3
@@ -89,13 +89,15 @@ class VCruiseHelper:
|
||||
return bool(getattr(starpilot_car_state, "decelHardCruise", False))
|
||||
return False
|
||||
|
||||
def update_v_cruise(self, CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state=None):
|
||||
def update_v_cruise(self, CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state=None,
|
||||
slc_target_with_offset=0.0):
|
||||
self.v_cruise_kph_last = self.v_cruise_kph
|
||||
|
||||
if CS.cruiseState.available:
|
||||
if self.gm_cc_only or self.redneck_non_pcm or not self.CP.pcmCruise:
|
||||
# if stock cruise is completely disabled, then we can use our own set speed logic
|
||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state)
|
||||
self._update_v_cruise_non_pcm(CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state,
|
||||
slc_target_with_offset)
|
||||
self.v_cruise_cluster_kph = self.v_cruise_kph
|
||||
self.update_button_timers(CS, enabled, starpilot_car_state)
|
||||
else:
|
||||
@@ -111,7 +113,8 @@ class VCruiseHelper:
|
||||
self.v_cruise_kph = V_CRUISE_UNSET
|
||||
self.v_cruise_cluster_kph = V_CRUISE_UNSET
|
||||
|
||||
def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state=None):
|
||||
def _update_v_cruise_non_pcm(self, CS, enabled, is_metric, speed_limit_changed, starpilot_toggles, starpilot_car_state=None,
|
||||
slc_target_with_offset=0.0):
|
||||
# handle button presses. TODO: this should be in state_control, but a decelCruise press
|
||||
# would have the effect of both enabling and changing speed is checked after the state transition
|
||||
if not enabled:
|
||||
@@ -169,11 +172,22 @@ class VCruiseHelper:
|
||||
short_interval, long_interval = self._get_cruise_delta_intervals(starpilot_toggles)
|
||||
v_cruise_delta_interval = long_interval if long_press or button_is_hard else short_interval
|
||||
v_cruise_delta = v_cruise_delta * v_cruise_delta_interval
|
||||
previous_v_cruise_kph = self.v_cruise_kph
|
||||
if v_cruise_delta_interval % 5 == 0 and self.v_cruise_kph % v_cruise_delta != 0: # partial interval
|
||||
self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta
|
||||
else:
|
||||
self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type]
|
||||
|
||||
# Round to stored cruise-speed precision so the SLC target can be matched exactly.
|
||||
slc_target_with_offset_kph = round(slc_target_with_offset * CV.MS_TO_KPH, 1)
|
||||
# Preserve an active SLC target when an accel increment would otherwise skip it.
|
||||
crossed_slc_target = (
|
||||
button_type in ACCEL_CRUISE_BUTTONS and
|
||||
previous_v_cruise_kph < slc_target_with_offset_kph < self.v_cruise_kph
|
||||
)
|
||||
if crossed_slc_target:
|
||||
self.v_cruise_kph = slc_target_with_offset_kph
|
||||
|
||||
# If set is pressed while overriding, clip cruise speed to minimum of vEgo
|
||||
if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise):
|
||||
self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH)
|
||||
|
||||
@@ -119,6 +119,47 @@ class TestVCruiseHelper:
|
||||
)
|
||||
assert pressed == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
|
||||
|
||||
def test_accel_stops_at_slc_target_before_crossing_it(self):
|
||||
self.starpilot_toggles.cruise_increase = 5
|
||||
self.v_cruise_helper.v_cruise_kph = 30
|
||||
self.v_cruise_helper.v_cruise_cluster_kph = 30
|
||||
slc_target_with_offset = 33 * CV.KPH_TO_MS
|
||||
|
||||
# A 30 km/h limit with a +3 km/h SLC offset should be an intermediate stop.
|
||||
for expected_kph in (33, 35, 40):
|
||||
for pressed in (True, False):
|
||||
CS = car.CarState(cruiseState={"available": True})
|
||||
CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)]
|
||||
self.v_cruise_helper.update_v_cruise(
|
||||
CS,
|
||||
enabled=True,
|
||||
is_metric=True,
|
||||
speed_limit_changed=False,
|
||||
starpilot_toggles=self.starpilot_toggles,
|
||||
slc_target_with_offset=slc_target_with_offset,
|
||||
)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(expected_kph)
|
||||
|
||||
def test_accel_uses_normal_interval_without_an_active_slc_target(self):
|
||||
self.starpilot_toggles.cruise_increase = 5
|
||||
self.v_cruise_helper.v_cruise_kph = 30
|
||||
self.v_cruise_helper.v_cruise_cluster_kph = 30
|
||||
|
||||
# Card passes zero when SLC is disabled or has no active speed-limit source.
|
||||
for pressed in (True, False):
|
||||
CS = car.CarState(cruiseState={"available": True})
|
||||
CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)]
|
||||
self.v_cruise_helper.update_v_cruise(
|
||||
CS,
|
||||
enabled=True,
|
||||
is_metric=True,
|
||||
speed_limit_changed=False,
|
||||
starpilot_toggles=self.starpilot_toggles,
|
||||
)
|
||||
|
||||
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(35)
|
||||
|
||||
def test_hard_press_uses_long_press_interval(self):
|
||||
self.enable(52 * CV.MPH_TO_MS, False)
|
||||
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
|
||||
|
||||
@@ -557,6 +557,29 @@ def test_set_speed_mode_overrides_on_raise_without_gas():
|
||||
controller.shutdown()
|
||||
|
||||
|
||||
def test_set_speed_mode_waits_until_above_slc_target_with_offset():
|
||||
controller = make_controller(
|
||||
is_metric=True,
|
||||
speed_limit_controller_override_manual=False,
|
||||
speed_limit_controller_override_set_speed=True,
|
||||
speed_limit_offset2=3 * CV.KPH_TO_MS,
|
||||
)
|
||||
try:
|
||||
controller.source = "Dashboard"
|
||||
controller.target = 30 * CV.KPH_TO_MS
|
||||
controller.last_valid_limit = controller.target
|
||||
|
||||
controller.update_override(30 * CV.KPH_TO_MS, 0.0, 30 * CV.KPH_TO_MS, 0.0, make_sm(gas_pressed=False))
|
||||
controller.update_override(33 * CV.KPH_TO_MS, 0.0, 30 * CV.KPH_TO_MS, 0.0, make_sm(gas_pressed=False))
|
||||
assert not controller.override_slc
|
||||
|
||||
controller.update_override(35 * CV.KPH_TO_MS, 0.0, 30 * CV.KPH_TO_MS, 0.0, make_sm(gas_pressed=False))
|
||||
assert controller.override_slc
|
||||
assert controller.overridden_speed == pytest.approx(35 * CV.KPH_TO_MS)
|
||||
finally:
|
||||
controller.shutdown()
|
||||
|
||||
|
||||
def test_set_speed_override_clears_on_new_speed_zone():
|
||||
# Entering a new (lower) posted limit clears the override; a steady high set speed must not
|
||||
# re-arm it. Only a fresh +/- press re-arms.
|
||||
|
||||
@@ -697,73 +697,105 @@ class FavoriteRadialMenu:
|
||||
lines[-1] = FavoriteRadialMenu._append_ellipsis(font, lines[-1], font_size, max_width)
|
||||
return lines
|
||||
|
||||
@staticmethod
|
||||
def _sample_aether_color(t: float, alpha: int) -> rl.Color:
|
||||
"""Sample from the tri-stop Aether gradient (#C49EFF -> #AC7DFF -> #58329E)."""
|
||||
if t <= 0.5:
|
||||
w = t * 2.0
|
||||
r = int(196 - 24 * w)
|
||||
g = int(158 - 33 * w)
|
||||
b = 255
|
||||
else:
|
||||
w = (t - 0.5) * 2.0
|
||||
r = int(172 - 84 * w)
|
||||
g = int(125 - 75 * w)
|
||||
b = int(255 - 97 * w)
|
||||
return rl.Color(r, g, b, alpha)
|
||||
|
||||
def _draw_corner_hint(self) -> None:
|
||||
scale = self._scale_for(self._rect)
|
||||
x0 = self._rect.x
|
||||
y0 = self._rect.y + self._rect.height
|
||||
is_pressed = self._corner_press is not None
|
||||
|
||||
size = 120.0 * scale
|
||||
purple = self._PURPLE
|
||||
size = 150.0 * scale
|
||||
steps = 48
|
||||
|
||||
# 1. Precompute edge vertices to minimize per-frame allocations
|
||||
v_origin = rl.Vector2(x0, y0)
|
||||
inv_steps = 1.0 / steps
|
||||
pts_top = [rl.Vector2(x0, y0 - size * (k * inv_steps)) for k in range(steps + 1)]
|
||||
pts_right = [rl.Vector2(x0 + size * (k * inv_steps), y0) for k in range(steps + 1)]
|
||||
|
||||
# 2. Pass 0 (Smoked Obsidian Base) & Pass 1 (Tri-Stop Aether Gradient Mesh)
|
||||
# Borderless design: smooth monotonic decay into exact 0 alpha at hypotenuse
|
||||
base_max_alpha = 200 if is_pressed else 160
|
||||
purple_max_alpha = 145 if is_pressed else 105
|
||||
|
||||
steps = 6
|
||||
max_alpha = 135 if is_pressed else 95
|
||||
for i in range(steps):
|
||||
t_a = i / float(steps)
|
||||
t_b = (i + 1) / float(steps)
|
||||
t_mid = (t_a + t_b) * 0.5
|
||||
t_mid = (i + 0.5) * inv_steps
|
||||
v_ta, v_tb = pts_top[i], pts_top[i + 1]
|
||||
v_ra, v_rb = pts_right[i], pts_right[i + 1]
|
||||
|
||||
# Non-linear quadratic fade: dissolves cleanly into transparent road video
|
||||
alpha = int(max_alpha * ((1.0 - t_mid) ** 1.7))
|
||||
if alpha <= 0:
|
||||
continue
|
||||
base_a = int(base_max_alpha * ((1.0 - t_mid) ** 1.40))
|
||||
purple_a = int(purple_max_alpha * ((1.0 - t_mid) ** 1.75))
|
||||
|
||||
slice_col = rl.Color(*purple, alpha)
|
||||
v_a_top = rl.Vector2(x0, y0 - size * t_a)
|
||||
v_a_right = rl.Vector2(x0 + size * t_a, y0)
|
||||
v_b_top = rl.Vector2(x0, y0 - size * t_b)
|
||||
v_b_right = rl.Vector2(x0 + size * t_b, y0)
|
||||
for col in (rl.Color(8, 6, 18, base_a) if base_a > 0 else None,
|
||||
self._sample_aether_color(t_mid, purple_a) if purple_a > 0 else None):
|
||||
if col is None:
|
||||
continue
|
||||
if i == 0:
|
||||
rl.draw_triangle(v_origin, v_rb, v_tb, col)
|
||||
else:
|
||||
rl.draw_triangle(v_ta, v_ra, v_tb, col)
|
||||
rl.draw_triangle(v_tb, v_ra, v_rb, col)
|
||||
|
||||
if i == 0:
|
||||
rl.draw_triangle(rl.Vector2(x0, y0), v_b_right, v_b_top, slice_col)
|
||||
else:
|
||||
rl.draw_triangle(v_a_top, v_a_right, v_b_top, slice_col)
|
||||
rl.draw_triangle(v_b_top, v_a_right, v_b_right, slice_col)
|
||||
# 3. Ultra-Polished Frosted-Glass Vector Arrow (Nestled deep in purple corner)
|
||||
cx = x0 + 34.0 * scale
|
||||
cy = y0 - 34.0 * scale
|
||||
|
||||
# 2. Elegant, optically centered vector arrow
|
||||
cx = x0 + size * 0.32
|
||||
cy = y0 - size * 0.32
|
||||
tip = rl.Vector2(cx + 15.0 * scale, cy - 15.0 * scale)
|
||||
tail = rl.Vector2(cx - 15.0 * scale, cy + 15.0 * scale)
|
||||
wing1 = rl.Vector2(tip.x - 14.0 * scale, tip.y + 1.2 * scale)
|
||||
wing2 = rl.Vector2(tip.x - 1.2 * scale, tip.y + 14.0 * scale)
|
||||
|
||||
tip = rl.Vector2(cx + 8.0 * scale, cy - 8.0 * scale)
|
||||
tail = rl.Vector2(cx - 7.0 * scale, cy + 7.0 * scale)
|
||||
wing1 = rl.Vector2(tip.x - 9.0 * scale, tip.y + 0.5 * scale)
|
||||
wing2 = rl.Vector2(tip.x - 0.5 * scale, tip.y + 9.0 * scale)
|
||||
line_w = 4.6 * scale
|
||||
|
||||
line_w = 3.0 * scale
|
||||
arrow_col = rl.Color(255, 255, 255, 235 if is_pressed else 195)
|
||||
shadow_col = rl.Color(16, 10, 28, 120)
|
||||
shadow_offset = 1.2 * scale
|
||||
# Tier 1: Deep Subsurface Ambient Occlusion Shadow
|
||||
s_off = 1.6 * scale
|
||||
s_tip = rl.Vector2(tip.x + s_off, tip.y + s_off)
|
||||
s_tail = rl.Vector2(tail.x + s_off, tail.y + s_off)
|
||||
s_w1 = rl.Vector2(wing1.x + s_off, wing1.y + s_off)
|
||||
s_w2 = rl.Vector2(wing2.x + s_off, wing2.y + s_off)
|
||||
shadow_w = line_w + 2.0 * scale
|
||||
shadow_col = rl.Color(8, 6, 16, 130 if is_pressed else 105)
|
||||
|
||||
# Soft ambient drop shadow behind arrow
|
||||
s_tip = rl.Vector2(tip.x + shadow_offset, tip.y + shadow_offset)
|
||||
s_tail = rl.Vector2(tail.x + shadow_offset, tail.y + shadow_offset)
|
||||
s_w1 = rl.Vector2(wing1.x + shadow_offset, wing1.y + shadow_offset)
|
||||
s_w2 = rl.Vector2(wing2.x + shadow_offset, wing2.y + shadow_offset)
|
||||
rl.draw_line_ex(s_tail, s_tip, line_w + 1.0 * scale, shadow_col)
|
||||
rl.draw_line_ex(s_tip, s_w1, line_w + 1.0 * scale, shadow_col)
|
||||
rl.draw_line_ex(s_tip, s_w2, line_w + 1.0 * scale, shadow_col)
|
||||
# Tier 2: Aether Violet Refractive Halo / Glass Bloom
|
||||
halo_w = line_w + 3.2 * scale
|
||||
halo_col = rl.Color(185, 145, 255, 75 if is_pressed else 55)
|
||||
|
||||
# Crisp arrow strokes
|
||||
rl.draw_line_ex(tail, tip, line_w, arrow_col)
|
||||
rl.draw_line_ex(tip, wing1, line_w, arrow_col)
|
||||
rl.draw_line_ex(tip, wing2, line_w, arrow_col)
|
||||
# Tier 3: Radiant High-Luminance Frost White Body
|
||||
arrow_col = rl.Color(255, 255, 255, 245 if is_pressed else 225)
|
||||
|
||||
# Smooth rounded joints
|
||||
r_cap = line_w * 0.5
|
||||
rl.draw_circle_v(tip, r_cap, arrow_col)
|
||||
rl.draw_circle_v(tail, r_cap, arrow_col)
|
||||
rl.draw_circle_v(wing1, r_cap, arrow_col)
|
||||
rl.draw_circle_v(wing2, r_cap, arrow_col)
|
||||
# Tier 4: Specular Spine Highlight
|
||||
spec_w = 2.0 * scale
|
||||
spec_col = rl.Color(255, 255, 255, 255 if is_pressed else 240)
|
||||
|
||||
# Render multi-pass optical stack
|
||||
layers = (
|
||||
(s_tail, s_tip, s_w1, s_w2, shadow_w, shadow_col),
|
||||
(tail, tip, wing1, wing2, halo_w, halo_col),
|
||||
(tail, tip, wing1, wing2, line_w, arrow_col),
|
||||
(tail, tip, wing1, wing2, spec_w, spec_col),
|
||||
)
|
||||
|
||||
for p_tail, p_tip, p_w1, p_w2, width, col in layers:
|
||||
rl.draw_line_ex(p_tail, p_tip, width, col)
|
||||
rl.draw_line_ex(p_tip, p_w1, width, col)
|
||||
rl.draw_line_ex(p_tip, p_w2, width, col)
|
||||
r_cap = width * 0.5
|
||||
for pt in (p_tail, p_tip, p_w1, p_w2):
|
||||
rl.draw_circle_v(pt, r_cap, col)
|
||||
|
||||
def _draw_radial_menu(self) -> None:
|
||||
scale = self._scale_for(self._rect)
|
||||
|
||||
Reference in New Issue
Block a user