Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] fa9c0d6d63 Update Python packages 2026-11-02 22:41:24 +00:00
15 changed files with 473 additions and 667 deletions
-17
View File
@@ -46,23 +46,6 @@ jobs:
CHECK_OOB: 0 CHECK_OOB: 0
GMMU: 0 GMMU: 0
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
- name: Resolve commaai's tinygrad pin, which their precompiled chestnut model is built and converted with
id: upstream-tg
env:
GH_TOKEN: ${{ github.token }}
run: |
read -r sha url < <(gh api repos/commaai/openpilot/contents/tinygrad_repo --jq '.sha + " " + .submodule_git_url')
echo "commaai/openpilot master pins $url@$sha, ours is $(git rev-parse HEAD:tinygrad_repo)"
dir="$RUNNER_TEMP/upstream_tinygrad"
git init -q "$dir" && git -C "$dir" fetch -q --depth 1 "$url" "$sha" && git -C "$dir" checkout -q FETCH_HEAD
echo "path=$dir" >> "$GITHUB_OUTPUT"
- name: Convert a pickled USB+AMD jit under commaai's tinygrad, as the chestnut model build does
env:
DEV: MOCKUSB+AMD
CHECK_OOB: 0
GMMU: 0
PYTHONPATH: ${{ steps.upstream-tg.outputs.path }}:${{ github.workspace }}
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
- name: Where tinygrad moved, for the helper or failure point named above - name: Where tinygrad moved, for the helper or failure point named above
if: failure() && github.event_name == 'pull_request' if: failure() && github.event_name == 'pull_request'
run: | run: |
-1
View File
@@ -149,7 +149,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"Version", {PERSISTENT, STRING}}, {"Version", {PERSISTENT, STRING}},
// --- sunnypilot params --- // // --- sunnypilot params --- //
{"AccelBoostSP", {PERSISTENT | BACKUP, BOOL, "1"}},
{"ApiCache_DriveStats", {PERSISTENT, JSON}}, {"ApiCache_DriveStats", {PERSISTENT, JSON}},
{"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}}, {"AutoLaneChangeBsmDelay", {PERSISTENT | BACKUP, BOOL, "0"}},
{"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}}, {"AutoLaneChangeTimer", {PERSISTENT | BACKUP, INT, "0"}},
@@ -57,12 +57,6 @@ class CruiseLayout(Widget):
description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."), description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
param="SmartCruiseControlMap") 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( self.custom_acc_toggle = toggle_item_sp(
title=tr("Custom ACC Speed Increments"), title=tr("Custom ACC Speed Increments"),
description="", description="",
@@ -92,7 +86,6 @@ class CruiseLayout(Widget):
self.icbm_toggle, self.icbm_toggle,
self.scc_v_toggle, self.scc_v_toggle,
self.scc_m_toggle, self.scc_m_toggle,
self.accel_boost_toggle,
self.custom_acc_toggle, self.custom_acc_toggle,
self.custom_acc_short_increment, self.custom_acc_short_increment,
self.custom_acc_long_increment, self.custom_acc_long_increment,
@@ -143,8 +136,6 @@ class CruiseLayout(Widget):
self.icbm_toggle.set_description(new_desc) self.icbm_toggle.set_description(new_desc)
self.icbm_toggle.show_description(True) self.icbm_toggle.show_description(True)
self.accel_boost_toggle.action_item.set_enabled(has_long)
if has_long or has_icbm: 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.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) self.scc_v_toggle.action_item.set_enabled(True)
@@ -160,7 +151,6 @@ class CruiseLayout(Widget):
else: else:
has_icbm = has_long = False has_icbm = has_long = False
self.icbm_toggle.action_item.set_enabled(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)) self.icbm_toggle.set_description(tr(ONROAD_ONLY_DESCRIPTION))
show_custom_acc_desc = False show_custom_acc_desc = False
@@ -66,75 +66,47 @@ def _patched(buf, topo) -> tuple[bytearray, dict]:
return blob, rows return blob, rows
def _packed_args(program) -> dict[int, int]: def _evaluate(u, env):
from tinygrad.device import TinyELF
params = getattr(program.arg, "params", None) or ()
return {off: p.arg.slot for (off, _), p in zip(TinyELF.iter_sig(tuple((None, p.addrspace, p.dtype, ()) for p in params)), params, strict=True)}
def _outer_slot(u, packed: dict[int, int]) -> int | None:
from tinygrad.uop.ops import Ops
if u.op is Ops.PARAM:
return None if packed else u.arg.slot
if u.op.name == "REFPARAM":
return u.arg.slot
if packed and u.op is Ops.LOAD and u.src[0].op is Ops.INDEX and _root(u.src[0].src[0]).op is Ops.PARAM:
index = _evaluate(u.src[0].src[1], {}, packed)
return None if index is None else packed.get(index * u.dtype.itemsize)
return None
def _evaluate(u, env, packed: dict[int, int]):
from tinygrad.uop.ops import GroupOp, Ops, exec_alu from tinygrad.uop.ops import GroupOp, Ops, exec_alu
if u.op in (Ops.AFTER, Ops.CAST, Ops.BITCAST): if u.op in (Ops.AFTER, Ops.CAST, Ops.BITCAST):
return _evaluate(u.src[0], env, packed) return _evaluate(u.src[0], env)
if u.op is Ops.CONST: if u.op is Ops.CONST:
return u.arg return u.arg
if u.op is Ops.RANGE: if u.op in (Ops.PARAM, Ops.RANGE):
return env.get(u) return env.get(u.arg.slot if u.op is Ops.PARAM else u)
if (slot := _outer_slot(u, packed)) is not None:
return env.get(slot)
if u.op is Ops.LOAD: if u.op is Ops.LOAD:
table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env, packed) table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env)
return struct.unpack_from(u.dtype.fmt, table.arg, index * u.dtype.itemsize)[0] if table.op is Ops.BINARY and index is not None else None return struct.unpack_from(u.dtype.fmt, table.arg, index * u.dtype.itemsize)[0] if table.op is Ops.BINARY and index is not None else None
if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env, packed) for x in u.src]): if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env) for x in u.src]):
return exec_alu(u.op, u.dtype, values, *([False] if "truncate_output" in inspect.signature(exec_alu).parameters else [])) return exec_alu(u.op, u.dtype, values, *([False] if "truncate_output" in inspect.signature(exec_alu).parameters else []))
return None return None
def _input_pokes(submit, kernargs) -> dict[int, tuple[int, int]]: def _input_pokes(submit, kernargs) -> dict[int, tuple[int, int]]:
from tinygrad.uop.ops import Ops from tinygrad.uop.ops import Ops
outer, packed = dict(enumerate(submit.src[1:])), _packed_args(submit.src[0]) outer = dict(enumerate(submit.src[1:]))
kernargs_params = {i for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]) is kernargs} kernargs_params = {i for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]) is kernargs}
inputs = {i: _root(a.src[0]).arg.slot for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]).op is Ops.PARAM} inputs = {i: _root(a.src[0]).arg.slot for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]).op is Ops.PARAM}
pokes = {} pokes = {}
for call in (u for u in submit.src[0].src[0].toposort() if u.op is Ops.CALL and len(u.src) > 3): for call in (u for u in submit.src[0].src[0].toposort() if u.op is Ops.CALL and len(u.src) > 3):
_, address, value, *_ = call.src[1:] _, address, value, *_ = call.src[1:]
used = list({slot for x in value.toposort() if (slot := _outer_slot(x, packed)) is not None} & inputs.keys()) used = list({x.arg.slot for x in value.toposort() if x.op is Ops.PARAM} & inputs.keys())
if len(used) != 1 or not kernargs_params & {_outer_slot(x, packed) for x in address.toposort()}: if len(used) != 1 or not kernargs_params & {x.arg.slot for x in address.toposort() if x.op is Ops.PARAM}:
continue continue
ranges = list(dict.fromkeys(x for x in (*address.toposort(), *value.toposort()) if x.op is Ops.RANGE)) ranges = list(dict.fromkeys(x for x in (*address.toposort(), *value.toposort()) if x.op is Ops.RANGE))
for trip in itertools.product(*[range(int(r.vmax) + 1) for r in ranges]): for trip in itertools.product(*[range(int(r.vmax) + 1) for r in ranges]):
env = dict.fromkeys(outer, 0) | dict(zip(ranges, trip, strict=True)) env = dict.fromkeys(outer, 0) | dict(zip(ranges, trip, strict=True))
if (offset := _evaluate(address, env, packed)) % 8 == 0: if (offset := _evaluate(address, env)) % 8 == 0:
pokes[offset] = (inputs[used[0]], _evaluate(value, env, packed)) pokes[offset] = (inputs[used[0]], _evaluate(value, env))
return pokes return pokes
def _elf(image: bytes, rodata: int, entry: int) -> bytes: def _elf(image: bytes, rodata: int) -> bytes:
spans = [(".text", 0, rodata), (".rodata", rodata, len(image))] if entry < rodata else \ names, offset, data, headers = b"\0.text\0.rodata\0.shstrtab\0", 64, b"", []
[(".data", 0, rodata), (".rodata", rodata, entry), (".text", entry, len(image))] for name, kind, address, content in [(0, 0, 0, b""), (1, 1, ALIGNMENT, image[:rodata]), (7, 1, ALIGNMENT + rodata, image[rodata:]), (15, 3, 0, names)]:
sections, names = [(0, 0, 0, 0, b"")], b"\0" headers.append(struct.pack("<IIQQQQIIQQ", name, kind, 2 if kind == 1 else 0, address, offset, len(content), 0, 0, 1, 0))
for name, start, end in (span for span in spans if span[1] < span[2]):
sections.append((len(names), 1, 6 if name == ".text" else 2, ALIGNMENT + start, image[start:end]))
names += name.encode() + b"\0"
sections.append((len(names), 3, 0, 0, names + b".shstrtab\0"))
offset, data, headers = 64, b"", []
for name, kind, flags, address, content in sections:
headers.append(struct.pack("<IIQQQQIIQQ", name, kind, flags, address, offset, len(content), 0, 0, 1, 0))
data, offset = data + content, offset + len(content) data, offset = data + content, offset + len(content)
header = struct.pack("<HHIQQQIHHHHHH", 1, 0xe0, 1, 0, 0, offset, 0, 64, 0, 0, 64, len(sections), len(sections) - 1) return b"\x7fELF\x02\x01\x01" + bytes(9) + struct.pack("<HHIQQQIHHHHHH", 1, 0xe0, 1, 0, 0, offset, 0, 64, 0, 0, 64, 4, 3) + data + b"".join(headers)
return b"\x7fELF\x02\x01\x01" + bytes(9) + header + data + b"".join(headers)
def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]: def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
@@ -183,7 +155,7 @@ def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
if image_alloc not in programs: if image_alloc not in programs:
image = bytes(_patched(image_alloc, topo)[0]) image = bytes(_patched(image_alloc, topo)[0])
rodata = next(r for r in range(0, len(image) - 24, 64) if r + struct.unpack_from("<q", image, r + 16)[0] == entry) rodata = next(r for r in range(0, len(image) - 24, 64) if r + struct.unpack_from("<q", image, r + 16)[0] == entry)
programs[image_alloc] = blob.add(_elf(image, rodata, entry)) programs[image_alloc] = blob.add(_elf(image, rodata))
start, size = _view(arguments.src[0]) start, size = _view(arguments.src[0])
args = [] args = []
for slot in range(start, start + unwrap(size), 8): for slot in range(start, start + unwrap(size), 8):
+2 -2
View File
@@ -264,8 +264,8 @@ def _program_uop(call: dict, lib: bytes):
parameter = UOp.param(call["globals"][compact_slot], _dtype(dtype), math.prod(shape)) parameter = UOp.param(call["globals"][compact_slot], _dtype(dtype), math.prod(shape))
image = (shape[0], shape[1]) if len(shape) == 3 and shape[2] == 4 else None image = (shape[0], shape[1]) if len(shape) == 3 and shape[2] == 4 else None
parameters.append(parameter.replace(arg=_fields(parameter.arg, name=name, image=image))) parameters.append(parameter.replace(arg=_fields(parameter.arg, name=name, image=image)))
info = _fields(ProgramInfo, global_size=tuple(call["global_size"]), local_size=tuple(call["local_size"]), params=tuple(parameters), info = _fields(ProgramInfo, global_size=tuple(call["global_size"]), local_size=tuple(call["local_size"]), globals=tuple(call["globals"]),
globals=tuple(call["globals"]), outs=tuple(call["outs"]), ins=tuple(call["ins"]), target=_fields(Target, **call["target"])) outs=tuple(call["outs"]), ins=tuple(call["ins"]), target=_fields(Target, **call["target"]))
return UOp(Ops.PROGRAM, src=(UOp(Ops.SINK, arg=KernelInfo(name=call["name"])), UOp(Ops.LINEAR, src=tuple(parameters)), return UOp(Ops.PROGRAM, src=(UOp(Ops.SINK, arg=KernelInfo(name=call["name"])), UOp(Ops.LINEAR, src=tuple(parameters)),
UOp(Ops.SOURCE, arg=""), UOp(Ops.BINARY, arg=lib)), arg=info) UOp(Ops.SOURCE, arg=""), UOp(Ops.BINARY, arg=lib)), arg=info)
+1 -1
View File
@@ -242,7 +242,7 @@ class ModelState(ModelStateBase):
desired_curvature = get_curvature_from_output(model_output, curvature_plan, v_ego, lat_action_t, self.mlsim) desired_curvature = get_curvature_from_output(model_output, curvature_plan, v_ego, lat_action_t, self.mlsim)
else: else:
desired_accel = model_output['action'][0, 1] desired_accel = model_output['action'][0, 1]
desired_curvature = model_output['action'][0, 0] / (max(2.5, v_ego))**2 desired_curvature = model_output['action'][0, 0] / (max(1.0, v_ego))**2
if blendful is not None: if blendful is not None:
desired_curvature += blendful(desired_curvature) desired_curvature += blendful(desired_curvature)
@@ -6,7 +6,6 @@ See the LICENSE.md file in the root directory for more details.
""" """
import os import os
import re import re
import struct
import sys import sys
import tempfile import tempfile
import unittest import unittest
@@ -26,8 +25,8 @@ if sys.platform == "darwin" and os.environ.get("DEV", "").startswith("MOCKUSB"):
from openpilot.common.test import OpenpilotTestCase from openpilot.common.test import OpenpilotTestCase
from openpilot.sunnypilot.modeld_v2 import convert_upstream_pkl from openpilot.sunnypilot.modeld_v2 import convert_upstream_pkl
from openpilot.sunnypilot.modeld_v2.convert_upstream_pkl import _elf, convert from openpilot.sunnypilot.modeld_v2.convert_upstream_pkl import convert
from openpilot.sunnypilot.modeld_v2.helpers import ALIGNMENT, PlainJit, load_model from openpilot.sunnypilot.modeld_v2.helpers import PlainJit, load_model
from openpilot.sunnypilot.modeld_v2.plain_artifact_compiler import capture_frame from openpilot.sunnypilot.modeld_v2.plain_artifact_compiler import capture_frame
IMG, DESIRE, STATE, OUT = (6, 8, 8), (8,), (4, 16), (1, 32) IMG, DESIRE, STATE, OUT = (6, 8, 8), (8,), (4, 16), (1, 32)
@@ -120,18 +119,3 @@ class TestConvertUpstreamPkl(OpenpilotTestCase):
bundle = load_model(out, cache=False, verify=True) bundle = load_model(out, cache=False, verify=True)
assert bundle["metadata"] == {"name": "tiny"} and bundle["input_specs"] == self.input_specs and bundle["output_specs"] == self.output_specs assert bundle["metadata"] == {"name": "tiny"} and bundle["input_specs"] == self.input_specs and bundle["output_specs"] == self.output_specs
self.assert_matches_eager(bundle["run"], 6) self.assert_matches_eager(bundle["run"], 6)
class TestKernelElf(OpenpilotTestCase):
def test_sections_follow_the_code(self):
from tinygrad.runtime.support.elf import elf_loader
code = bytes(range(1, 129))
for rodata, entry in ((len(code), 0), (0, 256)):
image = bytearray(code + bytes(64) if entry < rodata else bytes(256) + code)
struct.pack_into("<q", image, rodata + 16, entry - rodata)
loaded, sections, _ = elf_loader(_elf(bytes(image), rodata, entry))
headers = {section.name: section.header for section in sections}
assert bytes(loaded[ALIGNMENT:]) == bytes(image), "the uploaded program differs from the pickle's"
assert headers[".rodata"].sh_addr == ALIGNMENT + rodata, "the loader reads the kernel descriptor at .rodata"
text = headers[".text"]
assert text.sh_addr <= ALIGNMENT + entry < text.sh_addr + text.sh_size, "the amd mock sizes the code from the entry to the end of .text"
@@ -1,67 +0,0 @@
"""
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,8 +9,6 @@ from openpilot.cereal import messaging, custom
from opendbc.car import structs from opendbc.car import structs
from openpilot.common.constants import CV from openpilot.common.constants import CV
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX 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.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.smart_cruise_control.smart_cruise_control import SmartCruiseControl
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist
@@ -31,8 +29,6 @@ class LongitudinalPlannerSP:
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
self.source = LongitudinalPlanSource.cruise self.source = LongitudinalPlanSource.cruise
self.e2e_alerts_helper = E2EAlertsHelper() self.e2e_alerts_helper = E2EAlertsHelper()
self.accel_boost_sp = AccelBoostSP()
self.openpilot_longitudinal = CP.openpilotLongitudinalControl
self.output_v_target = 0. self.output_v_target = 0.
self.output_a_target = 0. self.output_a_target = 0.
@@ -68,9 +64,6 @@ class LongitudinalPlannerSP:
return self.output_v_target, self.output_a_target return self.output_v_target, self.output_a_target
def update(self, sm: messaging.SubMaster) -> None: 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.events_sp.clear()
self.e2e_alerts_helper.update(sm, self.events_sp) self.e2e_alerts_helper.update(sm, self.events_sp)
@@ -1,81 +0,0 @@
"""
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,19 +620,6 @@
} }
] ]
}, },
{
"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", "key": "IntelligentCruiseButtonManagement",
"widget": "toggle", "widget": "toggle",
@@ -30,12 +30,6 @@ sections:
label: Relaxed label: Relaxed
enablement: enablement:
- $ref: '#/macros/longitudinal' - $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 - key: IntelligentCruiseButtonManagement
widget: toggle widget: toggle
title: Intelligent Cruise Button Management (ICBM) (Alpha) title: Intelligent Cruise Button Management (ICBM) (Alpha)
+12 -27
View File
@@ -13,41 +13,26 @@ from pathlib import Path
from datetime import datetime, UTC from datetime import datetime, UTC
_VERSION_REGEX = re.compile(r'^(?:\d+[a-zA-Z]+|\d+[vVbB]\d+|[vVbB]\d+|\d{4})$')
_DECIMAL_VERSION_REGEX = re.compile(r'^([vVbB]?)(\d+(?:\.\d+)+)$')
def _version(word: str) -> str | None:
match = _DECIMAL_VERSION_REGEX.match(word.strip('()[]{}.,;:'))
if match:
return match.group(1).upper() + match.group(2)
stripped = re.sub(r'[^a-zA-Z0-9]', '', word)
return stripped.upper() if _VERSION_REGEX.match(stripped) else None
def _truncate(short_name: str) -> str:
return short_name[:8].rstrip('.')
def create_short_name(full_name: str) -> str: def create_short_name(full_name: str) -> str:
# Remove parentheses and extract alphanumeric words, keeping decimals for versioning # Remove parentheses and extract alphanumeric words
clean_name = re.sub(r'\([^)]*\)', '', full_name) clean_name = re.sub(r'\([^)]*\)', '', full_name)
words = [re.sub(r'[^a-zA-Z0-9.]', '', word) for word in clean_name.split()] words = [re.sub(r'[^a-zA-Z0-9]', '', word) for word in clean_name.split() if re.sub(r'[^a-zA-Z0-9]', '', word)]
words = [word for word in words if word]
if len(words) == 1: if len(words) == 1:
return _truncate((_version(words[0]) or words[0]).upper()) return words[0][:8].upper()
# Handle special case: Name + Version (e.g., "Word A1" -> "WordA1", "Model v1.1" -> "ModelV1.1") # Handle special case: Name + Version (e.g., "Word A1" -> "WordA1")
if len(words) == 2 and re.match(r'^[A-Za-z]?\d+$', words[1]): if len(words) == 2 and re.match(r'^[A-Za-z]\d+$', words[1]):
return _truncate((words[0] + (_version(words[1]) or words[1])).upper()) return (words[0] + words[1])[:8].upper()
result = "" result = ""
for word in words: for word in words:
# Version or number patterns # Version or number patterns
version = _version(word) if (re.match(r'^\d+[a-zA-Z]+$', word) or
if version: re.match(r'^\d+[vVbB]\d+$', word) or
result += version re.match(r'^[vVbB]\d+$', word) or
re.match(r'^\d{4}$', word)):
result += word.upper()
# All uppercase abbreviations (2-3 letters) # All uppercase abbreviations (2-3 letters)
elif re.match(r'^[A-Z]{2,3}$', word): elif re.match(r'^[A-Z]{2,3}$', word):
result += word result += word
@@ -60,7 +45,7 @@ def create_short_name(full_name: str) -> str:
result += word result += word
else: else:
result += word[0].upper() result += word[0].upper()
return _truncate(result) return result[:8]
def create_pkl_name(full_name: str) -> str: def create_pkl_name(full_name: str) -> str:
Generated
+438 -371
View File
File diff suppressed because it is too large Load Diff