Compare commits

...

8 Commits

Author SHA1 Message Date
Jason Wen 5242c65473 ui: drop restating accel boost comments 2026-11-03 22:36:12 -05:00
Jason Wen 9999d126a3 [MICI] ui: move accel boost ball swap out of stock 2026-11-03 22:35:03 -05:00
Jason Wen 656f764e33 controls: accel boost review fixes 2026-11-03 22:27:29 -05:00
discountchubbs 048727fb9f jason and kumar fought for hours to make this default so here we go 2026-11-03 13:21:03 -08:00
rav4kumar 0b68b8be9d ui: add MICI accel boost side-panel bar 2026-11-03 12:49:53 -07:00
discountchubbs 88b3ed1a20 lower entry threshold for fade, pos still builds. brake_probs still triggers at same hyp 2026-11-03 11:27:58 -08:00
discountchubbs 8b3a19eb04 ew 2026-11-03 08:48:06 -08:00
discountchubbs 367ff603d4 controls: predictive accel boost 2026-11-03 08:32:07 -08:00
12 changed files with 395 additions and 5 deletions
+1
View File
@@ -149,6 +149,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- //
{"AccelBoostSP", {PERSISTENT | BACKUP, BOOL, "1"}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -22,6 +22,7 @@ from openpilot.common.transformations.orientation import rot_from_euler
from enum import IntEnum
if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.accel_boost_indicator import AccelBoostIndicator
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
@@ -180,7 +181,7 @@ class AugmentedRoadView(CameraView):
self._long_indicator = LongIndicator()
self._alert_renderer = AlertRenderer()
self._driver_state_renderer = DriverStateRenderer()
self._confidence_ball = ConfidenceBall()
self._confidence_ball = AccelBoostIndicator(ConfidenceBall()) if gui_app.sunnypilot_ui() else ConfidenceBall()
self._offroad_label = UnifiedLabel("start the car to\nuse sunnypilot", 54, FontWeight.DISPLAY,
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
alignment=TextAlignment.CENTER,
@@ -57,6 +57,12 @@ class CruiseLayout(Widget):
description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
param="SmartCruiseControlMap")
self.accel_boost_toggle = toggle_item_sp(
title=tr("Acceleration Boost"),
description=tr("Adds a small push on top of sunnypilot's own acceleration, stronger in Aggressive and gentler in Relaxed. " +
"Eases off in curves and when sunnypilot slows or stops. Never goes past your set speed, and lead-car following still comes first."),
param="AccelBoostSP")
self.custom_acc_toggle = toggle_item_sp(
title=tr("Custom ACC Speed Increments"),
description="",
@@ -86,6 +92,7 @@ class CruiseLayout(Widget):
self.icbm_toggle,
self.scc_v_toggle,
self.scc_m_toggle,
self.accel_boost_toggle,
self.custom_acc_toggle,
self.custom_acc_short_increment,
self.custom_acc_long_increment,
@@ -136,6 +143,8 @@ class CruiseLayout(Widget):
self.icbm_toggle.set_description(new_desc)
self.icbm_toggle.show_description(True)
self.accel_boost_toggle.action_item.set_enabled(has_long)
if has_long or has_icbm:
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
self.scc_v_toggle.action_item.set_enabled(True)
@@ -151,6 +160,7 @@ class CruiseLayout(Widget):
else:
has_icbm = has_long = False
self.icbm_toggle.action_item.set_enabled(False)
self.accel_boost_toggle.action_item.set_enabled(False)
self.icbm_toggle.set_description(tr(ONROAD_ONLY_DESCRIPTION))
show_custom_acc_desc = False
@@ -0,0 +1,145 @@
"""
Copyright (c) 2021-, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import pyray as rl
from openpilot.cereal import log
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH
from openpilot.selfdrive.ui.mici.onroad.hud_renderer import SET_SPEED_PERSISTENCE
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import ACCEL_BOOST_MAX
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
BAR_WIDTH = 26
BAR_RADIUS = 0.5
TRACK_TOP_RATIO = 0.08
TRACK_BOTTOM_RATIO = 0.92
CAP_BLEND_START = 0.70
MAX_BOOST_BLEND = 0.55
COLOR_TRACK = rl.Color(255, 255, 255, 32)
COLOR_TRACK_EDGE = rl.Color(255, 255, 255, 70)
COLOR_HIGHLIGHT = rl.Color(255, 255, 255, 45)
AGGRESSIVE_COLOR = rl.Color(255, 80, 60, 255)
STANDARD_COLOR = rl.Color(0, 230, 120, 255)
RELAXED_COLOR = rl.Color(70, 150, 255, 255)
# The personality enum is ordered aggressive, standard, relaxed, matching ACCEL_BOOST_MAX
PROFILE_STYLES = {
0: (AGGRESSIVE_COLOR, ACCEL_BOOST_MAX[0]),
1: (STANDARD_COLOR, ACCEL_BOOST_MAX[1]),
2: (RELAXED_COLOR, ACCEL_BOOST_MAX[2]),
}
FADE_THRESHOLD = 0.01
def get_boost_style(personality) -> tuple[rl.Color, float]:
raw = getattr(personality, "raw", personality)
return PROFILE_STYLES.get(int(raw), PROFILE_STYLES[1])
def get_boost_fraction(boost: float, max_boost: float) -> float:
if max_boost <= 0.0:
return 0.0
return min(max(boost / max_boost, 0.0), 1.0)
def lighten(color: rl.Color, amount: float) -> rl.Color:
"""Lerp towards white in RGB, so the hue stays put the way HSV blending would not."""
amount = min(max(amount, 0.0), 1.0)
return rl.Color(int(color.r + (255 - color.r) * amount),
int(color.g + (255 - color.g) * amount),
int(color.b + (255 - color.b) * amount), color.a)
class AccelBoostIndicator(Widget):
def __init__(self, fallback: Widget | None = None, panel_width: float = SIDE_PANEL_WIDTH, bar_width: float = BAR_WIDTH):
super().__init__()
self._fallback = fallback
self._panel_width = panel_width
self._bar_width = bar_width
fps = max(gui_app.target_fps, 1)
self._boost_filter = FirstOrderFilter(0.0, 0.15, 1 / fps)
self._alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / fps)
self._max_boost_filter = FirstOrderFilter(0.0, 0.25, 1 / fps)
self._profile_color, self._max_boost = PROFILE_STYLES[1]
self._applied_time = float('-inf')
def _update_state(self):
sm = ui_state.sm
visible = (ui_state.accel_boost and ui_state.has_longitudinal_control and sm.alive['selfdriveState'] and
sm.alive['longitudinalPlan'] and sm['selfdriveState'].enabled)
plan = sm['longitudinalPlan']
# boost only moves the car while e2e wins the planner's min()
if visible and plan.accelBoost > FADE_THRESHOLD and plan.longitudinalPlanSource == log.LongitudinalPlan.LongitudinalPlanSource.e2e:
self._applied_time = rl.get_time()
visible = visible and rl.get_time() - self._applied_time < SET_SPEED_PERSISTENCE
self._alpha_filter.update(1.0 if visible else 0.0)
self._boost_filter.update(float(plan.accelBoost) if visible else 0.0)
if sm.alive['selfdriveState']:
self._profile_color, self._max_boost = get_boost_style(sm['selfdriveState'].personality)
self._max_boost_filter.update(self._max_boost)
def _render(self, _):
alpha = self._alpha_filter.x
if alpha < FADE_THRESHOLD or self._max_boost <= 0.0:
if self._fallback is not None:
self._fallback.render(self.rect)
return
bar_w = self._bar_width
cx = self.rect.x + self.rect.width - self._panel_width / 2
bar_x = cx - bar_w / 2
track_top = self.rect.y + self.rect.height * TRACK_TOP_RATIO
track_bottom = self.rect.y + self.rect.height * TRACK_BOTTOM_RATIO
track_h = max(1.0, track_bottom - track_top)
track_rect = rl.Rectangle(bar_x, track_top, bar_w, track_h)
rl.draw_rectangle_rounded(rl.Rectangle(bar_x - 3, track_top - 3, bar_w + 6, track_h + 6), BAR_RADIUS, 12,
rl.Color(0, 0, 0, int(80 * alpha)))
rl.draw_rectangle_rounded(track_rect, BAR_RADIUS, 12,
rl.Color(COLOR_TRACK.r, COLOR_TRACK.g, COLOR_TRACK.b, int(COLOR_TRACK.a * alpha)))
rl.draw_rectangle_rounded_lines_ex(track_rect, BAR_RADIUS, 12, 1,
rl.Color(COLOR_TRACK_EDGE.r, COLOR_TRACK_EDGE.g, COLOR_TRACK_EDGE.b,
int(COLOR_TRACK_EDGE.a * alpha)))
boost = max(self._boost_filter.x, 0.0)
ratio = get_boost_fraction(boost, self._max_boost_filter.x)
fill_h = ratio * track_h
if fill_h <= 0.0:
return
cap_blend = max(0.0, (ratio - CAP_BLEND_START) / (1.0 - CAP_BLEND_START))
fill_color = lighten(self._profile_color, MAX_BOOST_BLEND * cap_blend)
tip_y = track_bottom - fill_h
tip_center_y = tip_y + min(fill_h, bar_w) / 2
glow = rl.Color(fill_color.r, fill_color.g, fill_color.b, int(85 * alpha))
rl.draw_circle_gradient(rl.Vector2(cx, tip_center_y), int(bar_w * 0.85), glow, rl.BLANK)
fill = rl.Color(fill_color.r, fill_color.g, fill_color.b, int(255 * alpha))
if fill_h < bar_w:
# Keep the cap inside the track: a fixed radius would paint below the track near zero boost
radius = max(1, int(fill_h / 2))
rl.draw_circle(int(cx), int(track_bottom - radius), radius, fill)
else:
rl.draw_rectangle_rounded(rl.Rectangle(bar_x, tip_y, bar_w, fill_h), BAR_RADIUS, 12, fill)
if fill_h > 12:
highlight_rect = rl.Rectangle(bar_x + 4, tip_y + 5, 3, fill_h - 10)
rl.draw_rectangle_rounded(highlight_rect, 0.5, 8,
rl.Color(COLOR_HIGHLIGHT.r, COLOR_HIGHLIGHT.g, COLOR_HIGHLIGHT.b,
int(COLOR_HIGHLIGHT.a * alpha)))
rl.draw_circle(int(cx), int(tip_center_y), 2,
rl.Color(255, 255, 255, int(150 * alpha)))
@@ -0,0 +1,51 @@
"""
Copyright (c) 2021-, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from openpilot.cereal import log
from openpilot.common.test import OpenpilotTestCase
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.accel_boost_indicator import (
get_boost_style, get_boost_fraction, lighten,
AGGRESSIVE_COLOR, RELAXED_COLOR, STANDARD_COLOR,
)
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import ACCEL_BOOST_MAX
class TestAccelBoostIndicator(OpenpilotTestCase):
def test_profile_styles_match_controller_caps(self):
for personality, color, max_boost in (
(log.LongitudinalPersonality.aggressive, AGGRESSIVE_COLOR, ACCEL_BOOST_MAX[0]),
(log.LongitudinalPersonality.standard, STANDARD_COLOR, ACCEL_BOOST_MAX[1]),
(log.LongitudinalPersonality.relaxed, RELAXED_COLOR, ACCEL_BOOST_MAX[2])):
style_color, style_max = get_boost_style(personality)
assert style_color == color
assert style_max == max_boost
def test_wire_personality_picks_the_right_profile(self):
msg = log.SelfdriveState.new_message()
msg.personality = log.LongitudinalPersonality.aggressive
color, max_boost = get_boost_style(msg.personality)
assert color == AGGRESSIVE_COLOR
assert max_boost == ACCEL_BOOST_MAX[0]
def test_bar_fills_at_the_active_profile_cap(self):
for cap in ACCEL_BOOST_MAX:
assert get_boost_fraction(0.0, cap) == 0.0
assert get_boost_fraction(cap * 0.5, cap) == 0.5
assert get_boost_fraction(cap, cap) == 1.0
# A boost beyond the current cap (e.g. after switching to a shorter profile) still fills the bar
assert get_boost_fraction(max(ACCEL_BOOST_MAX), min(ACCEL_BOOST_MAX)) == 1.0
assert get_boost_fraction(0.0, 0.0) == 0.0
def test_cap_lighten_keeps_the_profile_hue(self):
# An HSV blend towards white sweeps relaxed blue through pink, so the fill lightens in RGB instead
relaxed = lighten(RELAXED_COLOR, 0.45)
aggressive = lighten(AGGRESSIVE_COLOR, 0.45)
assert relaxed.b > relaxed.r
assert relaxed.b >= relaxed.g
assert aggressive.r > aggressive.b
assert relaxed.r > RELAXED_COLOR.r
assert aggressive.g > AGGRESSIVE_COLOR.g
@@ -8,6 +8,7 @@ import pyray as rl
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.accel_boost_indicator import AccelBoostIndicator
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer, DeveloperUiState, get_bottom_dev_ui_offset
from openpilot.selfdrive.ui.sunnypilot.onroad.road_name import RoadNameRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel
@@ -37,6 +38,7 @@ class HudRendererSP(HudRenderer):
self.circular_alerts_renderer = CircularAlertsRenderer()
self.speed_renderer = SpeedRenderer()
self._torque_bar = TorqueBar(scale=3.0, always=True)
self._accel_boost_indicator = AccelBoostIndicator(panel_width=72, bar_width=36)
self.pcm_cruise_speed: bool = True
self.show_icbm_status: bool = False
@@ -131,11 +133,15 @@ class HudRendererSP(HudRenderer):
def _render(self, rect: rl.Rectangle) -> None:
super()._render(rect)
hud_rect = rect
if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH):
hud_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset())
if ui_state.torque_bar:
torque_rect = rect
if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH):
torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset())
self._torque_bar.render(torque_rect)
self._torque_bar.render(hud_rect)
boost_rect = hud_rect
if ui_state.developer_ui in (DeveloperUiState.RIGHT, DeveloperUiState.BOTH):
boost_rect = rl.Rectangle(hud_rect.x, hud_rect.y, hud_rect.width - 230, hud_rect.height)
self._accel_boost_indicator.render(boost_rect)
self.developer_ui.render(rect)
self.road_name_renderer.render(rect)
@@ -44,6 +44,7 @@ class UIStateSP:
self.screensaver = ScreenSaverSP(params=self.params)
self.screensaver_enabled: bool = False
self.accel_boost: bool = False
self.active_bundle = None
self.model_runner_tinygrad: bool = False
self.active_runner_tinygrad: bool = False
@@ -171,6 +172,7 @@ class UIStateSP:
# stock only counts the default big model's compiled pkl. a downloaded big bundle runs on the
# chestnut just the same, so ChestnutState has to see it as available too.
self.chestnut_compiled = self.chestnut_compiled or self.model_runner_tinygrad
self.accel_boost = self.params.get_bool("AccelBoostSP")
self.blindspot = self.params.get_bool("BlindSpot")
self.chevron_metrics = self.params.get("ChevronInfo")
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
@@ -0,0 +1,67 @@
"""
Copyright (c) 2021-, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
import numpy as np
from openpilot.cereal import log
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
ACCEL_BOOST_MIN_SPEED = 1.0
ACCEL_BOOST_MAX = (0.4, 0.25, 0.1) # aggressive, standard, relaxed, m/s^2
ACCEL_BOOST_BUILD, ACCEL_BOOST_RELEASE = 0.2, 1.0 # m/s^3
BRAKE_PROB = (0.1, 0.3) # the model's brake press in 4 s: it wants to slow
GAS_PROB = (0.6, 0.8) # predicts a human gas press at this hypothesis at about 23%
CURVE_LAT_ACCEL = (1.5, 2.5)
def get_boost_max(personality=log.LongitudinalPersonality.standard):
if personality == log.LongitudinalPersonality.relaxed:
return ACCEL_BOOST_MAX[2]
elif personality == log.LongitudinalPersonality.standard:
return ACCEL_BOOST_MAX[1]
elif personality == log.LongitudinalPersonality.aggressive:
return ACCEL_BOOST_MAX[0]
else:
raise NotImplementedError("Longitudinal personality not supported")
class AccelBoostSP:
def __init__(self):
self.params = Params()
self.frame = -1
self.enabled = False
self.boost = 0.0
self.total_boost = 0.0
def update_params(self):
self.frame += 1
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) != 0:
return False
enabled = self.params.get_bool("AccelBoostSP")
if enabled == self.enabled:
return False
self.enabled = enabled
self.boost = self.total_boost = 0.0
return True
def update(self, sm, output_a_target_e2e, output_a_target_mpc, a_cruise):
model = sm['modelV2']
brake_probs = model.meta.disengagePredictions.brakePressProbs
gas_probs = model.meta.disengagePredictions.gasPressProbs
if not sm['selfdriveState'].enabled or len(brake_probs) < 2 or len(gas_probs) < 2:
self.boost = self.total_boost = 0.0
return output_a_target_e2e
want = max(float(np.interp(brake_probs[1], BRAKE_PROB, [1.0, 0.0])), float(np.interp(gas_probs[1], GAS_PROB, [0.0, 1.0])))
want = 0.0 if model.action.shouldStop else want
want *= float(np.interp(abs(model.action.desiredCurvature) * sm['carState'].vEgo ** 2, CURVE_LAT_ACCEL, [1.0, 0.0]))
target = want * get_boost_max(sm['selfdriveState'].personality)
self.boost = float(np.clip(target, self.boost - ACCEL_BOOST_RELEASE * DT_MDL, self.boost + ACCEL_BOOST_BUILD * DT_MDL))
speed_scale = np.interp(sm['carState'].vEgo, [ACCEL_BOOST_MIN_SPEED, 2 * ACCEL_BOOST_MIN_SPEED], [0.0, 1.0])
self.total_boost = self.boost * speed_scale * np.interp(output_a_target_e2e, [-0.7, -0.2, 5.0], [0.0, 1.0, 1.0], right=0.0)
return output_a_target_e2e + self.total_boost
@@ -9,6 +9,8 @@ from openpilot.cereal import messaging, custom
from opendbc.car import structs
from openpilot.common.constants import CV
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX
from openpilot.selfdrive.controls.lib.accel_boost import AccelBoost
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import AccelBoostSP
from openpilot.sunnypilot.selfdrive.controls.lib.e2e_alerts_helper import E2EAlertsHelper
from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.smart_cruise_control import SmartCruiseControl
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist
@@ -29,6 +31,8 @@ class LongitudinalPlannerSP:
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
self.source = LongitudinalPlanSource.cruise
self.e2e_alerts_helper = E2EAlertsHelper()
self.accel_boost_sp = AccelBoostSP()
self.openpilot_longitudinal = CP.openpilotLongitudinalControl
self.output_v_target = 0.
self.output_a_target = 0.
@@ -64,6 +68,9 @@ class LongitudinalPlannerSP:
return self.output_v_target, self.output_a_target
def update(self, sm: messaging.SubMaster) -> None:
if self.accel_boost_sp.update_params():
self.accel_boost = self.accel_boost_sp if self.accel_boost_sp.enabled and self.openpilot_longitudinal else AccelBoost()
self.events_sp.clear()
self.e2e_alerts_helper.update(sm, self.events_sp)
@@ -0,0 +1,81 @@
"""
Copyright (c) 2021-, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from types import SimpleNamespace
from openpilot.common.parameterized import parameterized
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL
from openpilot.common.test import OpenpilotTestCase
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
from openpilot.sunnypilot.selfdrive.controls.lib.accel_boost import AccelBoostSP, ACCEL_BOOST_MAX
def make_sm(brake_prob=0.05, gas_prob=0.3, lat_accel=0.0, v_ego=30.0, personality=0, enabled=True, should_stop=False):
meta = SimpleNamespace(disengagePredictions=SimpleNamespace(brakePressProbs=[0.0, brake_prob, 0.0, 0.0, 0.0, 0.0],
gasPressProbs=[0.0, gas_prob, 0.0, 0.0, 0.0, 0.0]))
action = SimpleNamespace(shouldStop=should_stop, desiredCurvature=lat_accel / v_ego ** 2)
return {'modelV2': SimpleNamespace(meta=meta, action=action), 'carState': SimpleNamespace(vEgo=v_ego),
'selfdriveState': SimpleNamespace(enabled=enabled, personality=personality)}
def settle(sm, frames=100):
boost = AccelBoostSP()
for _ in range(frames):
boost.update(sm, 0.0, 9.0, 9.0)
return boost.total_boost
class TestAccelBoostSP(OpenpilotTestCase):
@parameterized.expand([(0,), (1,), (2,)])
def test_personality_max(self, personality):
assert abs(settle(make_sm(personality=personality)) - ACCEL_BOOST_MAX[personality]) < 1e-6
def test_brake_gate_closes(self):
assert settle(make_sm(brake_prob=0.5)) == 0.0
def test_gas_reopens(self):
assert abs(settle(make_sm(brake_prob=0.5, gas_prob=0.9)) - ACCEL_BOOST_MAX[0]) < 1e-6
def test_should_stop(self):
assert settle(make_sm(gas_prob=0.9, should_stop=True)) == 0.0
@parameterized.expand([(1.5, 1.0), (2.0, 0.5), (2.5, 0.0), (-2.0, 0.5)])
def test_curve_fade(self, lat_accel, scale):
assert abs(settle(make_sm(lat_accel=lat_accel)) - scale * ACCEL_BOOST_MAX[0]) < 1e-6
def test_low_speed(self):
assert settle(make_sm(v_ego=0.5)) == 0.0
def test_disabled_resets(self):
boost = AccelBoostSP()
for _ in range(100):
boost.update(make_sm(), 0.0, 9.0, 9.0)
assert boost.update(make_sm(enabled=False), 0.0, 9.0, 9.0) == 0.0
assert boost.boost == 0.0 and boost.total_boost == 0.0
def test_build_and_release_rates(self):
boost = AccelBoostSP()
boost.update(make_sm(), 0.0, 9.0, 9.0)
assert abs(boost.total_boost - 0.01) < 1e-6
for _ in range(100):
boost.update(make_sm(), 0.0, 9.0, 9.0)
boost.update(make_sm(brake_prob=0.5), 0.0, 9.0, 9.0)
assert abs(boost.total_boost - (ACCEL_BOOST_MAX[0] - 0.05)) < 1e-6
def test_param_switch(self):
params = Params()
boost = AccelBoostSP()
params.put_bool("AccelBoostSP", True, block=True)
assert boost.update_params() and boost.enabled
for _ in range(100):
boost.update(make_sm(), 0.0, 9.0, 9.0)
params.put_bool("AccelBoostSP", False, block=True)
for _ in range(int(PARAMS_UPDATE_PERIOD / DT_MDL) - 1):
assert not boost.update_params()
assert boost.update_params() and not boost.enabled
assert boost.boost == 0.0 and boost.total_boost == 0.0
@@ -620,6 +620,19 @@
}
]
},
{
"key": "AccelBoostSP",
"widget": "toggle",
"title": "Acceleration Boost",
"description": "Adds a small push on top of sunnypilot's own acceleration, stronger in Aggressive and gentler in Relaxed. Eases off in curves and when sunnypilot slows or stops. Never goes past your set speed, and lead-car following still comes first.",
"enablement": [
{
"type": "capability",
"field": "has_longitudinal_control",
"equals": true
}
]
},
{
"key": "IntelligentCruiseButtonManagement",
"widget": "toggle",
@@ -30,6 +30,12 @@ sections:
label: Relaxed
enablement:
- $ref: '#/macros/longitudinal'
- key: AccelBoostSP
widget: toggle
title: Acceleration Boost
description: "Adds a small push on top of sunnypilot's own acceleration, stronger in Aggressive and gentler in Relaxed. Eases off in curves and when sunnypilot slows or stops. Never goes past your set speed, and lead-car following still comes first."
enablement:
- $ref: '#/macros/longitudinal'
- key: IntelligentCruiseButtonManagement
widget: toggle
title: Intelligent Cruise Button Management (ICBM) (Alpha)