mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-11-06 05:43:47 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa9c0d6d63 |
@@ -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
-1
Submodule opendbc_repo updated: aa9bff6f54...3b4d6af8b1
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user