mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-05 12:03:45 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a699c11569 | |||
| 7d740b8a31 | |||
| a726bda691 | |||
| 72c450092d | |||
| 39649958ca | |||
| f65bb49d0a | |||
| b16bd8c7a9 |
+1
-2
@@ -39,6 +39,7 @@ tinygrad_model_runtime/** !filter !diff !merge -text
|
||||
*.a -text
|
||||
*.so -text
|
||||
*.so.* -text
|
||||
openpilot/cereal/messaging/bridge -text
|
||||
openpilot/selfdrive/pandad/pandad -text
|
||||
openpilot/tools/replay/replay -text
|
||||
openpilot/system/camerad/camerad -text
|
||||
@@ -47,8 +48,6 @@ openpilot/system/loggerd/encoderd -text
|
||||
openpilot/system/loggerd/loggerd -text
|
||||
openpilot/starpilot/galaxy/bin/frpc_linux_arm64 -text
|
||||
openpilot/starpilot/maps/provider/mapd -text
|
||||
openpilot/tools/arabesque_firefox/offline/*.deb -text
|
||||
openpilot/tools/arabesque_firefox/offline/*.tar.xz -text
|
||||
openpilot/selfdrive/test/process_replay/refs/starpilot/*.zst -text
|
||||
|
||||
# Published Panda object files are AArch64 ELF, not text.
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
id: setup
|
||||
- name: Build native dependencies for packaging tests
|
||||
id: build
|
||||
run: scons -j2 openpilot/common/libparams_c.so msgq_repo/msgq/_ipc.so
|
||||
run: scons -j2 openpilot/common/libparams_c.so msgq_repo/msgq/ipc_pyx.so
|
||||
- name: Test dependency tools
|
||||
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
|
||||
run: python3 -m unittest discover -s tools/vendor/tests -v
|
||||
@@ -225,7 +225,6 @@ jobs:
|
||||
openpilot/selfdrive/pandad/tests/test_pandad_canprotocol
|
||||
openpilot/selfdrive/pandad/tests/test_aol_protocol
|
||||
openpilot/selfdrive/pandad/tests/test_aol_wire
|
||||
openpilot/selfdrive/pandad/tests/test_panda_faults
|
||||
openpilot/selfdrive/pandad/tests/test_producer_timing
|
||||
openpilot/selfdrive/pandad/tests/test_producer_sink
|
||||
- name: Test map provider to host messaging
|
||||
|
||||
@@ -74,6 +74,7 @@ openpilot/selfdrive/pandad/tests/test_aol_wire
|
||||
openpilot/selfdrive/pandad/tests/test_panda_usb
|
||||
openpilot/cereal/services.h
|
||||
openpilot/cereal/gen
|
||||
openpilot/cereal/messaging/bridge
|
||||
openpilot/selfdrive/ui/translations/tmp
|
||||
openpilot/selfdrive/car/tests/cars_dump
|
||||
openpilot/system/camerad/camerad
|
||||
|
||||
+15
-16
@@ -57,7 +57,7 @@ assert arch in [
|
||||
"Darwin", # macOS arm64 (x86 not supported)
|
||||
]
|
||||
|
||||
pkg_names = ['acados', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zstd']
|
||||
pkg_names = ['acados', 'capnproto', 'ffmpeg', 'json11', 'ncurses', 'zeromq', 'zstd']
|
||||
if arch != 'Darwin':
|
||||
pkg_names.append('libusb')
|
||||
pkgs = [importlib.import_module(name) for name in pkg_names]
|
||||
@@ -172,7 +172,7 @@ env = Environment(
|
||||
COMPILATIONDB_USE_ABSPATH=True,
|
||||
REDNOSE_ROOT="#rednose_repo",
|
||||
tools=["default", "cython", "compilation_db", "rednose_filter"],
|
||||
toolpath=["#rednose_repo/site_scons/site_tools"],
|
||||
toolpath=["#msgq_repo/site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
|
||||
)
|
||||
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
|
||||
if arch == "Darwin":
|
||||
@@ -292,7 +292,7 @@ SConscript(['opendbc_repo/opendbc/can/SConscript'])
|
||||
# Build common module
|
||||
SConscript(['openpilot/common/SConscript'])
|
||||
Import('_common')
|
||||
common = [_common, 'json11']
|
||||
common = [_common, 'json11', 'zmq']
|
||||
Export('common')
|
||||
|
||||
# Build messaging (cereal + msgq + socketmaster + their dependencies)
|
||||
@@ -329,7 +329,6 @@ SConscript([
|
||||
'openpilot/selfdrive/ui/SConscript',
|
||||
'openpilot/starpilot/audio/native/SConscript',
|
||||
'openpilot/starpilot/models/runtime_bridge/SConscript',
|
||||
'openpilot/starpilot/ui/native/SConscript',
|
||||
'openpilot/starpilot/flm/SConscript',
|
||||
])
|
||||
|
||||
@@ -348,7 +347,7 @@ if GetOption('extras') and arch != "comma_arm64":
|
||||
env.CompilationDatabase('compile_commands.json')
|
||||
|
||||
# progress output
|
||||
def get_scons_nodes(nodes):
|
||||
def count_scons_nodes(nodes):
|
||||
seen = set()
|
||||
stack = list(nodes)
|
||||
|
||||
@@ -357,28 +356,28 @@ def get_scons_nodes(nodes):
|
||||
if node in seen:
|
||||
continue
|
||||
seen.add(node)
|
||||
# Only scan directories; generated sources are not ready yet.
|
||||
stack += (node.prerequisites or []) + node.children(scan=isinstance(node, SCons.Node.FS.Dir))
|
||||
executor = node.get_executor()
|
||||
if executor is not None:
|
||||
stack += executor.get_all_prerequisites() + executor.get_all_children()
|
||||
|
||||
return seen
|
||||
return len(seen)
|
||||
|
||||
progress_interval = 5
|
||||
progress_nodes = get_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry))
|
||||
progress_total = max(1, len(progress_nodes))
|
||||
progress_count = 0
|
||||
progress_total = max(1, count_scons_nodes(env.arg2nodes(BUILD_TARGETS or [Dir('.')], env.fs.Entry)))
|
||||
|
||||
def progress_function(node):
|
||||
if node not in progress_nodes:
|
||||
global progress_count
|
||||
if progress_count >= progress_total:
|
||||
return
|
||||
progress_nodes.remove(node)
|
||||
if len(progress_nodes) % progress_interval:
|
||||
return
|
||||
progress = round(100. * (1 - len(progress_nodes) / progress_total), 1)
|
||||
progress_count = min(progress_count + progress_interval, progress_total)
|
||||
progress = round(100. * progress_count / progress_total, 1)
|
||||
sys.stderr.write("\rBuilding: %5.1f%%" % progress if sys.stderr.isatty() else "progress: %.1f\n" % progress)
|
||||
if progress == 100. and sys.stderr.isatty():
|
||||
sys.stderr.write("\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
Progress(progress_function)
|
||||
Progress(progress_function, interval=progress_interval)
|
||||
AddPostAction(BUILD_TARGETS or [Dir('.')], prune_cache_dir)
|
||||
|
||||
# Release CI rebuilds tracked device outputs for the host architecture.
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
# Third-party notices
|
||||
|
||||
## Arabesque Firefox
|
||||
|
||||
The offline payload contains original Mozilla and Ubuntu archives. The pinned
|
||||
versions, upstream download URLs and hashes are listed in
|
||||
[`runtime.json`](openpilot/tools/arabesque_firefox/runtime.json). Mozilla publishes
|
||||
the [Firefox 153.6.0esr source](https://archive.mozilla.org/pub/firefox/releases/153.6.0esr/source/)
|
||||
and [Firefox notices](https://www.mozilla.org/en-US/about/legal/firefox/).
|
||||
The Ubuntu archives retain their package copyright files. The
|
||||
[Ubuntu package archive](https://ubuntu.com/project/docs/how-ubuntu-is-made/concepts/package-archive/)
|
||||
provides source packages alongside the binaries.
|
||||
|
||||
## Jetlink
|
||||
|
||||
Jetlink's comma client is copyright (c) 2026 Zeph Leggett and distributed under the MIT License. Its complete license and permission notice are retained in [jetlink_repo/LICENSE](jetlink_repo/LICENSE); source and revision are recorded in [CREDITS.md](CREDITS.md#jetlink-external-inference) and `upstream-sync.json`.
|
||||
|
||||
@@ -77,7 +77,7 @@ while [[ $# -gt 0 ]]; do
|
||||
;;
|
||||
--cereal|-cereal)
|
||||
shortcut_targets+=(openpilot/cereal/libcereal.a openpilot/cereal/libsocketmaster.a
|
||||
openpilot/cereal/services.h)
|
||||
openpilot/cereal/messaging/bridge openpilot/cereal/services.h)
|
||||
;;
|
||||
-j|--jobs)
|
||||
passthrough_args+=("$1")
|
||||
|
||||
+1
-26
@@ -5,8 +5,6 @@ openpilot ALC and openpilot LDW do not automatically drive the vehicle or reduce
|
||||
|
||||
While changing lanes, openpilot is not capable of looking next to you or checking your blind spot. Only nudge the wheel to initiate a lane change after you have confirmed it's safe to do so.
|
||||
|
||||
Unexpected steering may occur. openpilot ALC may not steer to avoid stopped or slow-moving vehicles, cross traffic, construction barriers or cones, motorcycles, children, pedestrians, animals, or other objects on the road.
|
||||
|
||||
Many factors can impact the performance of openpilot ALC and openpilot LDW, causing them to be unable to function as intended. These include, but are not limited to:
|
||||
|
||||
* Poor visibility (heavy rain, snow, fog, etc.) or weather conditions that may interfere with sensor operation.
|
||||
@@ -15,14 +13,6 @@ Many factors can impact the performance of openpilot ALC and openpilot LDW, caus
|
||||
* The device is mounted incorrectly.
|
||||
* When in sharp curves, like on-off ramps, intersections etc...; openpilot is designed to be limited in the amount of steering torque it can produce.
|
||||
* In the presence of restricted lanes or construction zones.
|
||||
* When the lane markings are unclear, worn, missing, or they cross each other.
|
||||
* Tar marks, shadows, road cracks, temporary or construction lane markings, or other road imperfections.
|
||||
* When lanes merge or split.
|
||||
* When entering a highway on-ramp or exiting an off-ramp.
|
||||
* On roads with intersections or roundabouts.
|
||||
* On slippery roads.
|
||||
* When towing a trailer.
|
||||
* When entering or exiting a tunnel.
|
||||
* When driving on highly banked roads or in presence of strong cross-wind.
|
||||
* Extremely hot or cold temperatures.
|
||||
* Bright light (due to oncoming headlights, direct sunlight, etc.).
|
||||
@@ -34,8 +24,6 @@ The list above does not represent an exhaustive list of situations that may inte
|
||||
|
||||
openpilot ACC and openpilot FCW are not systems that allow careless or inattentive driving. It is still necessary for the driver to pay close attention to the vehicle’s surroundings and to be ready to re-take control of the gas and the brake at all times.
|
||||
|
||||
openpilot ACC may not stop your vehicle in time to avoid a collision with a vehicle that quickly stops in front of you. openpilot ACC may occasionally cause your vehicle to brake when not required or when you are not expecting it.
|
||||
|
||||
Many factors can impact the performance of openpilot ACC and openpilot FCW, causing them to be unable to function as intended. These include, but are not limited to:
|
||||
|
||||
* Poor visibility (heavy rain, snow, fog, etc.) or weather conditions that may interfere with sensor operation.
|
||||
@@ -47,20 +35,9 @@ Many factors can impact the performance of openpilot ACC and openpilot FCW, caus
|
||||
* In presence of traffic signs or stop lights, which are not detected by openpilot at this time.
|
||||
* When the posted speed limit is below the user selected set speed. openpilot does not detect speed limits at this time.
|
||||
* In presence of vehicles in the same lane that are not moving.
|
||||
* When a vehicle you are following moves out of your driving path and a stationary or slow-moving vehicle or object is in front of you.
|
||||
* Narrow vehicles, such as bicycles or motorcycles.
|
||||
* Vehicles or objects only partially in the driving lane.
|
||||
* On curves, openpilot ACC may not detect a vehicle in the same lane, and may accelerate to the set speed.
|
||||
* Cross traffic.
|
||||
* Oncoming vehicles.
|
||||
* Animals or objects in the road.
|
||||
* Rare objects such as trailers, ramps, cargo, open doors, etc. protruding from vehicles.
|
||||
* When abrupt braking maneuvers are required. openpilot is designed to be limited in the amount of deceleration and acceleration that it can produce.
|
||||
* When surrounding vehicles perform close cut-ins from neighbor lanes.
|
||||
* Driving on hills, narrow, or winding roads.
|
||||
* On slippery roads.
|
||||
* When towing a trailer.
|
||||
* When entering or exiting a tunnel.
|
||||
* Extremely hot or cold temperatures.
|
||||
* Bright light (due to oncoming headlights, direct sunlight, etc.).
|
||||
* Interference from other equipment that generates radar waves.
|
||||
@@ -69,9 +46,7 @@ The list above does not represent an exhaustive list of situations that may inte
|
||||
|
||||
## Limitations of openpilot DM
|
||||
|
||||
openpilot DM displays a series of escalating warnings. Disregard of these warnings may result in openpilot being unavailable for a period of time.
|
||||
|
||||
openpilot DM should not be considered an exact measurement of the alertness of the driver. openpilot DM cannot always detect if the driver is tired or inattentive.
|
||||
openpilot DM should not be considered an exact measurement of the alertness of the driver.
|
||||
|
||||
Many factors can impact the performance of openpilot DM, causing it to be unable to function as intended. These include, but are not limited to:
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@ openpilot is an Adaptive Cruise Control (ACC) and Automated Lane Centering (ALC)
|
||||
Like other ACC and ALC systems, openpilot is a failsafe passive system and it requires the
|
||||
driver to be alert and to pay attention at all times.
|
||||
|
||||
openpilot is not an autonomous driving system. It is not a collision avoidance system or a
|
||||
substitute for safe and attentive driving. The driver is always responsible for the safe
|
||||
operation of the vehicle and must be ready to take control at any time. The driver can override
|
||||
openpilot at any time by braking, accelerating, or steering the vehicle. openpilot may
|
||||
disengage without warning. Read the [limitations](https://github.com/commaai/openpilot/blob/master/docs/LIMITATIONS.md) carefully before using openpilot.
|
||||
|
||||
To assist the driver in maintaining alertness, openpilot includes a driver monitoring feature
|
||||
that alerts when it detects driver distraction.
|
||||
|
||||
|
||||
+1
-10
@@ -26,18 +26,9 @@ fi
|
||||
export QCOM_PRIORITY=12
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="20.2.1"
|
||||
export AGNOS_VERSION="19.8.1"
|
||||
fi
|
||||
|
||||
export AGNOS_UPDATE_POLICY="auto"
|
||||
# C3 selects its complete matching firmware chain in the AGNOS manifest loader.
|
||||
# Unknown hardware retains its installed OS.
|
||||
if [ -f /AGNOS ] || [ -e /sys/firmware/devicetree/base/model ]; then
|
||||
case "$(tr -d '\0' 2>/dev/null < /sys/firmware/devicetree/base/model)" in
|
||||
"comma tici"|"comma tizi"|"comma mici") ;;
|
||||
*) export AGNOS_UPDATE_POLICY="retain" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
|
||||
@@ -20,6 +20,3 @@ libmessaging.*
|
||||
libmessaging_shared.*
|
||||
.sconsign.dblite
|
||||
.mypy_cache/
|
||||
|
||||
/msgq/_ipc.pyi
|
||||
/msgq/visionipc/_visionipc.pyi
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
include README.md pyproject.toml setup.py SConstruct SConscript
|
||||
recursive-include msgq *.py py.typed *.h *.cc
|
||||
global-exclude *.pyi *.so *.o *.os *.a *.pyc
|
||||
recursive-include site_scons *.py
|
||||
recursive-include msgq *.py *.pyx *.pxd *.h *.cc
|
||||
global-exclude *.so *.o *.os *.a *.pyc
|
||||
|
||||
+4
-33
@@ -1,29 +1,7 @@
|
||||
import sys
|
||||
import sysconfig
|
||||
from pathlib import Path
|
||||
|
||||
import nanobind
|
||||
|
||||
Import('env', 'arch', 'common')
|
||||
|
||||
envNanobind = env.Clone(LIBS=[])
|
||||
nb_dir = Path(nanobind.source_dir()).parent
|
||||
envNanobind.Append(CPPPATH=[sysconfig.get_paths()['include'], nanobind.include_dir(), str(nb_dir / "ext/robin_map/include")],
|
||||
CCFLAGS=["-fvisibility=hidden", "-fno-strict-aliasing"])
|
||||
if arch == "Darwin":
|
||||
envNanobind.Append(LINKFLAGS=["-bundle", "-undefined", "dynamic_lookup"])
|
||||
else:
|
||||
# Python symbols resolve at import time.
|
||||
envNanobind["LINKFLAGS"] = [f for f in env["LINKFLAGS"] if f != "-Wl,--no-undefined"] + ["-pthread", "-shared"]
|
||||
|
||||
# Third-party runtime sources do not follow our shadowing warning policy.
|
||||
envNanobindRuntime = envNanobind.Clone()
|
||||
envNanobindRuntime['CCFLAGS'] = [flag for flag in envNanobindRuntime['CCFLAGS'] if not flag.startswith('-Wshadow')]
|
||||
nanobind_lib = envNanobindRuntime.SharedObject('msgq/nb_combined.os', str(nb_dir / "src/nb_combined.cpp"))
|
||||
Import('env', 'envCython', 'common')
|
||||
|
||||
|
||||
visionipc_dir = Dir('msgq/visionipc')
|
||||
python_suffix = ".so"
|
||||
|
||||
# Build msgq
|
||||
msgq_objects = env.SharedObject([
|
||||
@@ -32,7 +10,7 @@ msgq_objects = env.SharedObject([
|
||||
'msgq/msgq.cc',
|
||||
])
|
||||
msgq = env.Library('msgq', msgq_objects)
|
||||
msgq_python = envNanobind.Program(f'msgq/_ipc{python_suffix}', ['msgq/ipc_py.cc', nanobind_lib], LIBS=envNanobind["LIBS"]+[msgq]+common)
|
||||
msgq_python = envCython.Program('msgq/ipc_pyx.so', 'msgq/ipc_pyx.pyx', LIBS=envCython["LIBS"]+[msgq]+common)
|
||||
|
||||
# Build Vision IPC
|
||||
vipc_files = ['visionipc.cc', 'visionipc_server.cc', 'visionipc_client.cc']
|
||||
@@ -46,17 +24,10 @@ vipc_objects = env.SharedObject(vipc_sources)
|
||||
visionipc = env.Library('visionipc', vipc_objects)
|
||||
|
||||
|
||||
vipc_libs = envNanobind["LIBS"] + [visionipc, msgq] + common
|
||||
visionipc_python = envNanobind.Program(f'{visionipc_dir.abspath}/_visionipc{python_suffix}', [f'{visionipc_dir.abspath}/visionipc_py.cc', nanobind_lib],
|
||||
vipc_libs = envCython["LIBS"] + [visionipc, msgq] + common
|
||||
envCython.Program(f'{visionipc_dir.abspath}/visionipc_pyx.so', f'{visionipc_dir.abspath}/visionipc_pyx.pyx',
|
||||
LIBS=vipc_libs)
|
||||
|
||||
# ASan modules require a preloaded runtime before Python can import them.
|
||||
if "-fsanitize=address" not in env["LINKFLAGS"]:
|
||||
root = Dir('.').abspath
|
||||
for module in ('msgq._ipc', 'msgq.visionipc._visionipc'):
|
||||
env.Command(module.replace('.', '/') + '.pyi', [msgq_python, visionipc_python],
|
||||
f'"{sys.executable}" -m nanobind.stubgen -i "{root}" -m {module} -o "$TARGET"')
|
||||
|
||||
if GetOption('extras'):
|
||||
env.Program('msgq/test_runner', ['msgq/msgq_tests.cc'], LIBS=[msgq]+common)
|
||||
|
||||
|
||||
+15
-1
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sysconfig
|
||||
|
||||
arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
|
||||
if platform.system() == "Darwin":
|
||||
@@ -13,6 +14,7 @@ cpppath = [
|
||||
"#/",
|
||||
'#msgq/',
|
||||
'/usr/lib/include',
|
||||
sysconfig.get_paths()['include'],
|
||||
]
|
||||
|
||||
AddOption('--minimal',
|
||||
@@ -60,9 +62,21 @@ env = Environment(
|
||||
CFLAGS="-std=gnu11",
|
||||
CXXFLAGS="-std=c++1z",
|
||||
CPPPATH=cpppath,
|
||||
tools=["default"]
|
||||
CYTHONCFILESUFFIX=".cpp",
|
||||
tools=["default", "cython"]
|
||||
)
|
||||
|
||||
Export('env', 'arch', 'common')
|
||||
|
||||
envCython = env.Clone(LIBS=[])
|
||||
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"]
|
||||
envCython["CCFLAGS"].remove('-Werror')
|
||||
if arch == "Darwin":
|
||||
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"]
|
||||
else:
|
||||
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
|
||||
|
||||
Export('envCython')
|
||||
|
||||
|
||||
SConscript(['SConscript'])
|
||||
|
||||
@@ -18,12 +18,12 @@ test:
|
||||
ty:
|
||||
run: ty check .
|
||||
codespell:
|
||||
run: codespell {files} -L ned,stdio,master --builtin clear,rare,informal,usage,code,names,en-GB_to_en-US -S uv.lock
|
||||
run: codespell {files} -L ned,stdio,master --builtin clear,rare,informal,usage,code,names,en-GB_to_en-US -S uv.lock,*_pyx.cpp
|
||||
files: git ls-tree -r HEAD --name-only
|
||||
cppcheck:
|
||||
run: cppcheck --error-exitcode=1 --inline-suppr --language=c++ --force --quiet -j4 --check-level=exhaustive $(git ls-files '*.cc' | grep -v -E '(msgq_tests|test_runner)\.cc')
|
||||
cpplint:
|
||||
run: cpplint --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces msgq/
|
||||
run: cpplint --exclude=msgq/ipc_pyx.cpp --exclude=msgq/visionipc/visionipc_pyx.cpp --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces msgq/
|
||||
|
||||
# *** tests ***
|
||||
test_runner:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
ipc_pyx.cpp
|
||||
@@ -1,8 +1,8 @@
|
||||
from msgq._ipc import Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
||||
from msgq.ipc_pyx import Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \
|
||||
set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event
|
||||
from msgq._ipc import MultiplePublishersError, IpcError
|
||||
from msgq.ipc_pyx import MultiplePublishersError, IpcError
|
||||
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Union
|
||||
|
||||
__all__ = [
|
||||
"Poller",
|
||||
@@ -25,7 +25,7 @@ __all__ = [
|
||||
|
||||
NO_TRAVERSAL_LIMIT = 2**64-1
|
||||
|
||||
def fake_event_handle(endpoint: str, identifier: Optional[str] = None, override: bool = True, enable: bool = False) -> SocketEventHandle:
|
||||
def fake_event_handle(endpoint: str, identifier: Optional[Union[str, bytes]] = None, override: bool = True, enable: bool = False) -> SocketEventHandle:
|
||||
ident = identifier if identifier is not None else get_fake_prefix()
|
||||
handle = SocketEventHandle(endpoint, ident, override)
|
||||
if override:
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,61 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
|
||||
|
||||
cdef extern from "msgq/event.h":
|
||||
cdef cppclass Event:
|
||||
@staticmethod
|
||||
int wait_for_one(vector[Event], int) except +
|
||||
|
||||
Event()
|
||||
Event(int)
|
||||
void set()
|
||||
int clear()
|
||||
void wait(int) except +
|
||||
bool peek()
|
||||
int fd()
|
||||
|
||||
cdef cppclass SocketEventHandle:
|
||||
@staticmethod
|
||||
void toggle_fake_events(bool)
|
||||
@staticmethod
|
||||
void set_fake_prefix(string)
|
||||
@staticmethod
|
||||
string fake_prefix()
|
||||
|
||||
SocketEventHandle(string, string, bool)
|
||||
bool is_enabled()
|
||||
void set_enabled(bool)
|
||||
Event recv_called()
|
||||
Event recv_ready()
|
||||
|
||||
|
||||
cdef extern from "msgq/ipc.h":
|
||||
cdef cppclass Message:
|
||||
void init(size_t)
|
||||
void init(char *, size_t)
|
||||
void close()
|
||||
size_t getSize()
|
||||
char *getData()
|
||||
|
||||
cdef cppclass SubSocket:
|
||||
SubSocket() nogil
|
||||
int connect(string, bool, size_t) nogil
|
||||
Message * receive(bool) nogil
|
||||
void setTimeout(int) nogil
|
||||
|
||||
cdef cppclass PubSocket:
|
||||
PubSocket()
|
||||
int connect(string, size_t)
|
||||
int sendMessage(Message *)
|
||||
int send(char *, size_t)
|
||||
bool all_readers_updated()
|
||||
|
||||
cdef cppclass Poller:
|
||||
Poller()
|
||||
void registerSocket(SubSocket *)
|
||||
vector[SubSocket*] poll(int) nogil
|
||||
@@ -1,113 +0,0 @@
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
#include <nanobind/stl/optional.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
#include <nanobind/stl/vector.h>
|
||||
|
||||
#include "msgq/event.h"
|
||||
#include "msgq/ipc.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace {
|
||||
|
||||
struct IpcError : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct MultiplePublishersError : IpcError {
|
||||
using IpcError::IpcError;
|
||||
};
|
||||
|
||||
void check_error(bool failed, int error, const std::string &endpoint = "") {
|
||||
if (!failed) return;
|
||||
std::string message = "Messaging failure " + (endpoint.empty() ? "" : "with " + endpoint) + ": " + std::strerror(error);
|
||||
if (error == EADDRINUSE) throw MultiplePublishersError(message);
|
||||
throw IpcError(message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NB_MODULE(_ipc, m) {
|
||||
// Daemon threads can still hold sockets at exit.
|
||||
nb::set_leak_warnings(false);
|
||||
|
||||
nb::exception<IpcError> ipc_error(m, "IpcError");
|
||||
nb::exception<MultiplePublishersError>(m, "MultiplePublishersError", ipc_error);
|
||||
|
||||
m.def("toggle_fake_events", &SocketEventHandle::toggle_fake_events, nb::arg("enabled"));
|
||||
m.def("set_fake_prefix", &SocketEventHandle::set_fake_prefix, nb::arg("prefix"));
|
||||
m.def("get_fake_prefix", &SocketEventHandle::fake_prefix);
|
||||
m.def("delete_fake_prefix", [] { SocketEventHandle::set_fake_prefix(""); });
|
||||
m.def("wait_for_one_event", &Event::wait_for_one, nb::arg("events"), nb::arg("timeout") = -1, nb::call_guard<nb::gil_scoped_release>());
|
||||
|
||||
nb::class_<Event>(m, "Event")
|
||||
.def(nb::init<>())
|
||||
.def("set", &Event::set)
|
||||
.def("clear", &Event::clear)
|
||||
.def("wait", &Event::wait, nb::arg("timeout") = -1, nb::call_guard<nb::gil_scoped_release>())
|
||||
.def("peek", &Event::peek)
|
||||
.def_prop_ro("fd", &Event::fd)
|
||||
.def_prop_ro("ptr", [](Event &event) { return reinterpret_cast<uintptr_t>(&event); });
|
||||
|
||||
nb::class_<SocketEventHandle>(m, "SocketEventHandle")
|
||||
.def(nb::init<std::string, std::string, bool>(), nb::arg("endpoint"), nb::arg("identifier"), nb::arg("override"))
|
||||
.def_prop_rw("enabled", &SocketEventHandle::is_enabled, &SocketEventHandle::set_enabled)
|
||||
.def_prop_ro("recv_called_event", &SocketEventHandle::recv_called, nb::keep_alive<0, 1>())
|
||||
.def_prop_ro("recv_ready_event", &SocketEventHandle::recv_ready, nb::keep_alive<0, 1>());
|
||||
|
||||
nb::class_<SubSocket>(m, "SubSocket")
|
||||
.def(nb::init<>())
|
||||
.def("connect", [](SubSocket &socket, const std::string &name, bool conflate, size_t segment_size) {
|
||||
nb::gil_scoped_release release;
|
||||
int result = socket.connect(name, conflate, segment_size);
|
||||
check_error(result != 0, errno, name);
|
||||
}, nb::arg("endpoint"), nb::arg("conflate") = false, nb::arg("segment_size") = 0)
|
||||
.def("setTimeout", &SubSocket::setTimeout, nb::arg("timeout"), nb::call_guard<nb::gil_scoped_release>())
|
||||
.def("receive", [](SubSocket &socket, bool non_blocking) -> std::optional<nb::bytes> {
|
||||
std::unique_ptr<Message> message;
|
||||
{
|
||||
nb::gil_scoped_release release;
|
||||
message.reset(socket.receive(non_blocking));
|
||||
}
|
||||
if (!message) return std::nullopt;
|
||||
return nb::bytes(message->getData(), message->getSize());
|
||||
}, nb::arg("non_blocking") = false);
|
||||
|
||||
nb::class_<Poller>(m, "Poller")
|
||||
.def(nb::init<>())
|
||||
.def("registerSocket", &Poller::registerSocket, nb::arg("socket"), nb::keep_alive<1, 2>())
|
||||
.def("poll", &Poller::poll, nb::arg("timeout"), nb::rv_policy::reference, nb::call_guard<nb::gil_scoped_release>());
|
||||
|
||||
nb::class_<PubSocket>(m, "PubSocket")
|
||||
.def(nb::init<>())
|
||||
.def("connect", [](PubSocket &socket, const std::string &name, size_t segment_size) {
|
||||
int result = socket.connect(name, segment_size);
|
||||
check_error(result != 0, errno, name);
|
||||
}, nb::arg("endpoint"), nb::arg("segment_size") = 0)
|
||||
.def("send", [](PubSocket &socket, nb::bytes data) {
|
||||
int result = socket.send(const_cast<char *>(data.c_str()), data.size());
|
||||
check_error(result != static_cast<int>(data.size()), errno);
|
||||
}, nb::arg("data"))
|
||||
.def("all_readers_updated", &PubSocket::all_readers_updated)
|
||||
.def("wait_for_readers", [](PubSocket &socket, double timeout, double interval) {
|
||||
// Python's sleep releases the GIL and handles signals while waiting.
|
||||
nb::object sleep = nb::module_::import_("time").attr("sleep");
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
if (socket.all_readers_updated()) return;
|
||||
sleep(interval);
|
||||
}
|
||||
PyErr_SetString(PyExc_TimeoutError, "subscriber did not connect");
|
||||
throw nb::python_error();
|
||||
}, nb::arg("timeout") = 1.0, nb::arg("interval") = 0.001);
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
import time
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp cimport bool
|
||||
from libc cimport errno
|
||||
from libc.string cimport strerror
|
||||
from cython.operator import dereference
|
||||
|
||||
|
||||
from .ipc cimport SubSocket as cppSubSocket
|
||||
from .ipc cimport PubSocket as cppPubSocket
|
||||
from .ipc cimport Poller as cppPoller
|
||||
from .ipc cimport Message as cppMessage
|
||||
from .ipc cimport Event as cppEvent, SocketEventHandle as cppSocketEventHandle
|
||||
|
||||
|
||||
class IpcError(Exception):
|
||||
def __init__(self, endpoint=None):
|
||||
suffix = f"with {endpoint.decode('utf-8')}" if endpoint else ""
|
||||
message = f"Messaging failure {suffix}: {strerror(errno.errno).decode('utf-8')}"
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class MultiplePublishersError(IpcError):
|
||||
pass
|
||||
|
||||
|
||||
def toggle_fake_events(bool enabled):
|
||||
cppSocketEventHandle.toggle_fake_events(enabled)
|
||||
|
||||
|
||||
def set_fake_prefix(string prefix):
|
||||
cppSocketEventHandle.set_fake_prefix(prefix)
|
||||
|
||||
|
||||
def get_fake_prefix():
|
||||
return cppSocketEventHandle.fake_prefix()
|
||||
|
||||
|
||||
def delete_fake_prefix():
|
||||
cppSocketEventHandle.set_fake_prefix(b"")
|
||||
|
||||
|
||||
def wait_for_one_event(list events, int timeout=-1):
|
||||
cdef vector[cppEvent] items
|
||||
for event in events:
|
||||
items.push_back(dereference(<cppEvent*><size_t>event.ptr))
|
||||
return cppEvent.wait_for_one(items, timeout)
|
||||
|
||||
|
||||
cdef class Event:
|
||||
cdef cppEvent event;
|
||||
|
||||
cdef setEvent(self, cppEvent event):
|
||||
self.event = event
|
||||
|
||||
def set(self):
|
||||
self.event.set()
|
||||
|
||||
def clear(self):
|
||||
return self.event.clear()
|
||||
|
||||
def wait(self, int timeout=-1):
|
||||
self.event.wait(timeout)
|
||||
|
||||
def peek(self):
|
||||
return self.event.peek()
|
||||
|
||||
@property
|
||||
def fd(self):
|
||||
return self.event.fd()
|
||||
|
||||
@property
|
||||
def ptr(self):
|
||||
return <size_t><void*>&self.event
|
||||
|
||||
|
||||
cdef class SocketEventHandle:
|
||||
cdef cppSocketEventHandle * handle;
|
||||
|
||||
def __cinit__(self, string endpoint, string identifier, bool override):
|
||||
self.handle = new cppSocketEventHandle(endpoint, identifier, override)
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.handle
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.handle.is_enabled()
|
||||
|
||||
@enabled.setter
|
||||
def enabled(self, bool value):
|
||||
self.handle.set_enabled(value)
|
||||
|
||||
@property
|
||||
def recv_called_event(self):
|
||||
e = Event()
|
||||
e.setEvent(self.handle.recv_called())
|
||||
|
||||
return e
|
||||
|
||||
@property
|
||||
def recv_ready_event(self):
|
||||
e = Event()
|
||||
e.setEvent(self.handle.recv_ready())
|
||||
|
||||
return e
|
||||
|
||||
|
||||
cdef class Poller:
|
||||
cdef cppPoller * poller
|
||||
cdef list sub_sockets
|
||||
|
||||
def __cinit__(self):
|
||||
self.sub_sockets = []
|
||||
self.poller = new cppPoller()
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.poller
|
||||
|
||||
def registerSocket(self, SubSocket socket):
|
||||
self.sub_sockets.append(socket)
|
||||
self.poller.registerSocket(socket.socket)
|
||||
|
||||
def poll(self, timeout):
|
||||
sockets = []
|
||||
cdef int t = timeout
|
||||
|
||||
with nogil:
|
||||
result = self.poller.poll(t)
|
||||
|
||||
for s in result:
|
||||
socket = SubSocket()
|
||||
socket.setPtr(s)
|
||||
sockets.append(socket)
|
||||
|
||||
return sockets
|
||||
|
||||
|
||||
cdef class SubSocket:
|
||||
cdef cppSubSocket * socket
|
||||
cdef bool is_owner
|
||||
def __cinit__(self):
|
||||
with nogil:
|
||||
self.socket = new cppSubSocket()
|
||||
|
||||
self.is_owner = True
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.is_owner:
|
||||
with nogil:
|
||||
del self.socket
|
||||
|
||||
cdef setPtr(self, cppSubSocket * ptr):
|
||||
if self.is_owner:
|
||||
with nogil:
|
||||
del self.socket
|
||||
|
||||
self.is_owner = False
|
||||
self.socket = ptr
|
||||
|
||||
def connect(self, string endpoint, bool conflate=False, size_t segment_size=0):
|
||||
cdef int r
|
||||
with nogil:
|
||||
r = self.socket.connect(endpoint, conflate, segment_size)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError(endpoint)
|
||||
else:
|
||||
raise IpcError(endpoint)
|
||||
|
||||
def setTimeout(self, int timeout):
|
||||
with nogil:
|
||||
self.socket.setTimeout(timeout)
|
||||
|
||||
def receive(self, bool non_blocking=False):
|
||||
cdef cppMessage *msg
|
||||
with nogil:
|
||||
msg = self.socket.receive(non_blocking)
|
||||
if msg == NULL:
|
||||
return None
|
||||
else:
|
||||
sz = msg.getSize()
|
||||
m = msg.getData()[:sz]
|
||||
with nogil:
|
||||
del msg
|
||||
return m
|
||||
|
||||
cdef class PubSocket:
|
||||
cdef cppPubSocket * socket
|
||||
|
||||
def __cinit__(self):
|
||||
self.socket = new cppPubSocket()
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.socket
|
||||
|
||||
def connect(self, string endpoint, size_t segment_size=0):
|
||||
r = self.socket.connect(endpoint, segment_size)
|
||||
|
||||
if r != 0:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError(endpoint)
|
||||
else:
|
||||
raise IpcError(endpoint)
|
||||
|
||||
def send(self, bytes data):
|
||||
length = len(data)
|
||||
r = self.socket.send(<char*>data, length)
|
||||
|
||||
if r != length:
|
||||
if errno.errno == errno.EADDRINUSE:
|
||||
raise MultiplePublishersError
|
||||
else:
|
||||
raise IpcError
|
||||
|
||||
def all_readers_updated(self):
|
||||
return self.socket.all_readers_updated()
|
||||
|
||||
def wait_for_readers(self, double timeout=1.0, double interval=0.001):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if self.all_readers_updated():
|
||||
return
|
||||
time.sleep(interval)
|
||||
raise TimeoutError("subscriber did not connect")
|
||||
Executable
BIN
Binary file not shown.
@@ -569,98 +569,3 @@ TEST_CASE("32 active subscribers retain every message across publisher restart")
|
||||
msgq_close_queue(&pub);
|
||||
std::remove(file.c_str());
|
||||
}
|
||||
|
||||
static int64_t millis_since(std::chrono::steady_clock::time_point start)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count();
|
||||
}
|
||||
|
||||
static std::thread send_later(msgq_queue_t *q, int delay_ms)
|
||||
{
|
||||
return std::thread([=]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
|
||||
uint64_t value = 1;
|
||||
msgq_msg_t msg;
|
||||
msgq_msg_init_data(&msg, (char *)&value, sizeof(value));
|
||||
msgq_msg_send(&msg, q);
|
||||
msgq_msg_close(&msg);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_poll times out")
|
||||
{
|
||||
cleanup_test_queue();
|
||||
msgq_queue_t writer, reader;
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
|
||||
msgq_pollitem_t items[] = {{&reader, 0}};
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
REQUIRE(msgq_poll(items, 1, 100) == 0);
|
||||
REQUIRE(millis_since(start) >= 100);
|
||||
REQUIRE(msgq_poll(items, 1, 0) == 0);
|
||||
|
||||
send_later(&writer, 0).join();
|
||||
REQUIRE(msgq_poll(items, 1, 0) == 1);
|
||||
|
||||
msgq_close_queue(&writer);
|
||||
msgq_close_queue(&reader);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_poll wakes up on send")
|
||||
{
|
||||
cleanup_test_queue();
|
||||
msgq_queue_t writer, reader;
|
||||
msgq_new_queue(&writer, "test_queue", 1024);
|
||||
msgq_new_queue(&reader, "test_queue", 1024);
|
||||
msgq_init_publisher(&writer);
|
||||
msgq_init_subscriber(&reader);
|
||||
|
||||
msgq_pollitem_t items[] = {{&reader, 0}};
|
||||
std::thread sender = send_later(&writer, 50);
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int n = msgq_poll(items, 1, 5000);
|
||||
int64_t elapsed = millis_since(start);
|
||||
sender.join();
|
||||
|
||||
REQUIRE(n == 1);
|
||||
REQUIRE(elapsed < 1000);
|
||||
|
||||
msgq_close_queue(&writer);
|
||||
msgq_close_queue(&reader);
|
||||
}
|
||||
|
||||
TEST_CASE("msgq_poll multiple queues")
|
||||
{
|
||||
cleanup_test_queue();
|
||||
remove("/dev/shm/msgq_test_queue2");
|
||||
remove("/tmp/msgq_test_queue2");
|
||||
msgq_queue_t writer1, writer2, reader1, reader2;
|
||||
msgq_new_queue(&writer1, "test_queue", 1024);
|
||||
msgq_new_queue(&writer2, "test_queue2", 1024);
|
||||
msgq_new_queue(&reader1, "test_queue", 1024);
|
||||
msgq_new_queue(&reader2, "test_queue2", 1024);
|
||||
msgq_init_publisher(&writer1);
|
||||
msgq_init_publisher(&writer2);
|
||||
msgq_init_subscriber(&reader1);
|
||||
msgq_init_subscriber(&reader2);
|
||||
|
||||
msgq_pollitem_t items[] = {{&reader1, 0}, {&reader2, 0}};
|
||||
std::thread sender = send_later(&writer2, 50);
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int n = msgq_poll(items, 2, 5000);
|
||||
int64_t elapsed = millis_since(start);
|
||||
sender.join();
|
||||
|
||||
REQUIRE(n == 1);
|
||||
REQUIRE(elapsed < 1000);
|
||||
REQUIRE(items[0].revents == 0);
|
||||
REQUIRE(items[1].revents == 1);
|
||||
|
||||
msgq_close_queue(&writer1);
|
||||
msgq_close_queue(&writer2);
|
||||
msgq_close_queue(&reader1);
|
||||
msgq_close_queue(&reader2);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
visionipc_pyx.cpp
|
||||
*.so
|
||||
@@ -1,4 +1,4 @@
|
||||
from msgq.visionipc._visionipc import VisionBuf, VisionIpcClient, VisionIpcServer, get_endpoint_name
|
||||
from msgq.visionipc.visionipc_pyx import VisionBuf, VisionIpcClient, VisionIpcServer, get_endpoint_name
|
||||
|
||||
__all__ = [
|
||||
"VisionBuf",
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
import gc
|
||||
import uuid
|
||||
import struct
|
||||
import unittest
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from msgq.visionipc import VisionIpcServer, VisionIpcClient
|
||||
|
||||
@@ -11,6 +10,7 @@ class TestVisionIpc(unittest.TestCase):
|
||||
client: Optional[VisionIpcClient]
|
||||
|
||||
def setUp(self):
|
||||
# Each test needs independent listener and publisher endpoints in parallel runs.
|
||||
self.name = f"test_{uuid.uuid4().hex}"
|
||||
self.server = None
|
||||
self.client = None
|
||||
@@ -19,139 +19,88 @@ class TestVisionIpc(unittest.TestCase):
|
||||
self.client = None
|
||||
self.server = None
|
||||
|
||||
def setup_vipc(
|
||||
self, name: str, stream_type: int, *other_stream_types: int,
|
||||
num_buffers: int = 1, width: int = 100, height: int = 100, conflate: bool = False,
|
||||
) -> tuple[VisionIpcServer, VisionIpcClient, int]:
|
||||
self.server = server = VisionIpcServer(self.name)
|
||||
for stream in (stream_type, *other_stream_types):
|
||||
server.create_buffers(stream, num_buffers, width, height)
|
||||
server.start_listener()
|
||||
def setup_vipc(self, *stream_types, num_buffers=1, width=100, height=100, conflate=False):
|
||||
self.server = VisionIpcServer(self.name)
|
||||
for stream_type in stream_types:
|
||||
self.server.create_buffers(stream_type, num_buffers, width, height)
|
||||
self.server.start_listener()
|
||||
|
||||
self.client = client = VisionIpcClient(self.name, stream_type, conflate)
|
||||
assert client.connect(True)
|
||||
buffer_len = client.buffer_len
|
||||
assert buffer_len is not None
|
||||
return server, client, buffer_len
|
||||
if len(stream_types):
|
||||
self.client = VisionIpcClient(self.name, stream_types[0], conflate)
|
||||
assert self.client.connect(True)
|
||||
else:
|
||||
self.client = None
|
||||
|
||||
return self.server, self.client
|
||||
|
||||
def test_connect(self):
|
||||
_, client, _ = self.setup_vipc("camerad", 0)
|
||||
assert client.is_connected()
|
||||
self.setup_vipc(0)
|
||||
assert self.client is not None
|
||||
assert self.client.is_connected()
|
||||
|
||||
def test_available_streams(self):
|
||||
stream_types = (0, 2)
|
||||
self.setup_vipc("camerad", *stream_types)
|
||||
self.setup_vipc(*stream_types)
|
||||
available_streams = VisionIpcClient.available_streams(self.name, True)
|
||||
assert available_streams == set(stream_types)
|
||||
|
||||
def test_buffers(self):
|
||||
width, height, num_buffers = 100, 200, 5
|
||||
_, client, buffer_len = self.setup_vipc("camerad", 0, num_buffers=num_buffers, width=width, height=height)
|
||||
assert client.width == width
|
||||
assert client.height == height
|
||||
assert buffer_len > 0
|
||||
assert client.num_buffers == num_buffers
|
||||
self.setup_vipc(0, num_buffers=num_buffers, width=width, height=height)
|
||||
assert self.client is not None
|
||||
assert self.client.width == width
|
||||
assert self.client.height == height
|
||||
assert self.client.buffer_len is not None and self.client.buffer_len > 0
|
||||
assert self.client.num_buffers == num_buffers
|
||||
|
||||
def test_send_single_buffer(self):
|
||||
server, client, buffer_len = self.setup_vipc("camerad", 0)
|
||||
buf = bytearray(buffer_len)
|
||||
self.setup_vipc(0)
|
||||
assert self.server is not None
|
||||
assert self.client is not None
|
||||
assert self.client.buffer_len is not None
|
||||
buf = bytearray(self.client.buffer_len)
|
||||
struct.pack_into("<Q", buf, 0, 1234)
|
||||
server.send(0, buf, frame_id=1337)
|
||||
self.server.send(0, buf, frame_id=1337)
|
||||
|
||||
recv_buf = client.recv()
|
||||
recv_buf = self.client.recv()
|
||||
assert recv_buf is not None
|
||||
data = recv_buf.data
|
||||
assert isinstance(data, memoryview)
|
||||
assert struct.unpack_from("<Q", data, 0)[0] == 1234
|
||||
assert len(data) == buffer_len
|
||||
assert data[8:].nbytes == buffer_len - 8
|
||||
assert client.frame_id == 1337
|
||||
assert len(data) == self.client.buffer_len
|
||||
assert data[8:].nbytes == self.client.buffer_len - 8
|
||||
assert self.client.frame_id == 1337
|
||||
assert recv_buf.frame_id == 1337
|
||||
|
||||
def test_no_conflate(self):
|
||||
server, client, buffer_len = self.setup_vipc("camerad", 0)
|
||||
buf = bytearray(buffer_len)
|
||||
server.send(0, buf, frame_id=1)
|
||||
server.send(0, buf, frame_id=2)
|
||||
self.setup_vipc(0)
|
||||
assert self.server is not None
|
||||
assert self.client is not None
|
||||
assert self.client.buffer_len is not None
|
||||
buf = bytearray(self.client.buffer_len)
|
||||
self.server.send(0, buf, frame_id=1)
|
||||
self.server.send(0, buf, frame_id=2)
|
||||
|
||||
recv_buf = client.recv()
|
||||
recv_buf = self.client.recv()
|
||||
assert recv_buf is not None
|
||||
assert client.frame_id == 1
|
||||
assert self.client.frame_id == 1
|
||||
|
||||
recv_buf = client.recv()
|
||||
recv_buf = self.client.recv()
|
||||
assert recv_buf is not None
|
||||
assert client.frame_id == 2
|
||||
assert self.client.frame_id == 2
|
||||
|
||||
def test_conflate(self):
|
||||
server, client, buffer_len = self.setup_vipc("camerad", 0, conflate=True)
|
||||
buf = bytearray(buffer_len)
|
||||
server.send(0, buf, frame_id=1)
|
||||
server.send(0, buf, frame_id=2)
|
||||
self.setup_vipc(0, conflate=True)
|
||||
assert self.server is not None
|
||||
assert self.client is not None
|
||||
assert self.client.buffer_len is not None
|
||||
buf = bytearray(self.client.buffer_len)
|
||||
self.server.send(0, buf, frame_id=1)
|
||||
self.server.send(0, buf, frame_id=2)
|
||||
|
||||
recv_buf = client.recv()
|
||||
recv_buf = self.client.recv()
|
||||
assert recv_buf is not None
|
||||
assert client.frame_id == 2
|
||||
assert self.client.frame_id == 2
|
||||
|
||||
recv_buf = client.recv(timeout_ms=5)
|
||||
recv_buf = self.client.recv(timeout_ms=5)
|
||||
assert recv_buf is None
|
||||
|
||||
def test_send_buffer_types(self):
|
||||
server, client, buffer_len = self.setup_vipc("camerad", 0)
|
||||
data = bytes(i % 256 for i in range(buffer_len))
|
||||
strided = bytearray(len(data) * 2)
|
||||
strided[::2] = data
|
||||
for payload in (data, bytearray(data), memoryview(data), memoryview(strided)[::2], memoryview(data[::-1])[::-1]):
|
||||
with self.subTest(type=type(payload)):
|
||||
server.send(0, payload, frame_id=42, timestamp_sof=123, timestamp_eof=456)
|
||||
buf = client.recv()
|
||||
assert buf is not None
|
||||
self.assertEqual(bytes(buf.data), data)
|
||||
self.assertEqual(buf.frame_id, 42)
|
||||
self.assertEqual(client.timestamp_sof, 123)
|
||||
self.assertEqual(client.timestamp_eof, 456)
|
||||
self.assertFalse(client.valid)
|
||||
|
||||
def test_send_wrong_size(self):
|
||||
server, _, _ = self.setup_vipc("camerad", 0)
|
||||
with self.assertRaises(AssertionError):
|
||||
server.send(0, b"short")
|
||||
|
||||
def test_buffer_keeps_client_alive(self):
|
||||
server, client, buffer_len = self.setup_vipc("camerad", 0)
|
||||
data = b"a" * buffer_len
|
||||
server.send(0, data, frame_id=42)
|
||||
buf = client.recv()
|
||||
assert buf is not None
|
||||
self.client = None
|
||||
del client
|
||||
gc.collect()
|
||||
self.assertEqual(buf.frame_id, 42)
|
||||
view = buf.data
|
||||
del buf
|
||||
gc.collect()
|
||||
self.assertEqual(bytes(view), data)
|
||||
self.assertFalse(view.readonly)
|
||||
view[0] = ord("b")
|
||||
self.assertEqual(view[0], ord("b"))
|
||||
|
||||
def test_unconnected_properties(self):
|
||||
client = VisionIpcClient("unconnected", 0, False)
|
||||
for value in (client.width, client.height, client.stride, client.uv_offset, client.buffer_len):
|
||||
self.assertIsNone(value)
|
||||
self.assertEqual(client.num_buffers, 0)
|
||||
self.assertFalse(client.is_connected())
|
||||
self.assertFalse(client.connect(False))
|
||||
self.assertIsNone(client.recv(timeout_ms=0))
|
||||
|
||||
def test_exact_uint64_timestamps_survive_receive_and_timeout(self):
|
||||
server, client, size = self.setup_vipc("clock", 0, width=32, height=18)
|
||||
payload = bytes(size)
|
||||
for frame, sof, eof in ((0, 0, 0), (1, 2**53 + 1, 2**53 + 19),
|
||||
(2**32 - 1, 2**63 + 17, 2**64 - 1)):
|
||||
with self.subTest(frame=frame, sof=sof, eof=eof):
|
||||
server.send(0, payload, frame_id=frame, timestamp_sof=sof, timestamp_eof=eof)
|
||||
buffer = client.recv(timeout_ms=1000)
|
||||
assert buffer is not None
|
||||
assert (client.frame_id, client.timestamp_sof, client.timestamp_eof) == (frame, sof, eof)
|
||||
assert client.recv(timeout_ms=0) is None
|
||||
assert (client.frame_id, client.timestamp_sof, client.timestamp_eof) == (frame, sof, eof)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
from libcpp.set cimport set
|
||||
from libc.stdint cimport uint32_t, uint64_t
|
||||
from libcpp cimport bool, int
|
||||
|
||||
cdef extern from "msgq/visionipc/visionbuf.h":
|
||||
ctypedef uint32_t VisionStreamType "VisionStreamType"
|
||||
|
||||
cdef cppclass VisionBuf:
|
||||
void * addr
|
||||
int fd
|
||||
size_t len
|
||||
size_t width
|
||||
size_t height
|
||||
size_t stride
|
||||
size_t uv_offset
|
||||
size_t idx
|
||||
void set_frame_id(uint64_t id)
|
||||
uint64_t get_frame_id()
|
||||
|
||||
cdef extern from "msgq/visionipc/visionipc.h":
|
||||
struct VisionIpcBufExtra:
|
||||
uint32_t frame_id
|
||||
uint64_t timestamp_sof
|
||||
uint64_t timestamp_eof
|
||||
bool valid
|
||||
|
||||
cdef extern from "msgq/visionipc/visionipc_server.h":
|
||||
string get_endpoint_name(string, VisionStreamType)
|
||||
|
||||
cdef cppclass VisionIpcServer:
|
||||
VisionIpcServer(string)
|
||||
void create_buffers(VisionStreamType, size_t, size_t, size_t)
|
||||
void create_buffers_with_sizes(VisionStreamType, size_t, size_t, size_t, size_t, size_t, size_t)
|
||||
VisionBuf * get_buffer(VisionStreamType)
|
||||
void send(VisionBuf *, VisionIpcBufExtra *, bool)
|
||||
void start_listener()
|
||||
|
||||
cdef extern from "msgq/visionipc/visionipc_client.h":
|
||||
cdef cppclass VisionIpcClient:
|
||||
int num_buffers
|
||||
VisionBuf buffers[1]
|
||||
VisionIpcClient(string, VisionStreamType, bool)
|
||||
VisionBuf * recv(VisionIpcBufExtra *, int) nogil
|
||||
bool connect(bool)
|
||||
bool is_connected()
|
||||
@staticmethod
|
||||
set[VisionStreamType] getAvailableStreams(string, bool)
|
||||
@@ -1,109 +0,0 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <nanobind/nanobind.h>
|
||||
#include <nanobind/ndarray.h>
|
||||
#include <nanobind/stl/optional.h>
|
||||
#include <nanobind/stl/set.h>
|
||||
#include <nanobind/stl/string.h>
|
||||
|
||||
#include "msgq/visionipc/visionbuf.h"
|
||||
#include "msgq/visionipc/visionipc.h"
|
||||
#include "msgq/visionipc/visionipc_client.h"
|
||||
#include "msgq/visionipc/visionipc_server.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
namespace {
|
||||
|
||||
struct PythonVisionIpcClient : VisionIpcClient {
|
||||
using VisionIpcClient::VisionIpcClient;
|
||||
VisionIpcBufExtra extra = {};
|
||||
};
|
||||
|
||||
using Buffer = nb::ndarray<nb::memview, uint8_t, nb::ndim<1>, nb::device::cpu>;
|
||||
using InputBuffer = nb::ndarray<const uint8_t, nb::ndim<1>, nb::device::cpu>;
|
||||
|
||||
} // namespace
|
||||
|
||||
NB_MODULE(_visionipc, m) {
|
||||
// Daemon threads can still hold clients and servers at exit.
|
||||
nb::set_leak_warnings(false);
|
||||
|
||||
m.def("get_endpoint_name", &get_endpoint_name, nb::arg("name"), nb::arg("stream"));
|
||||
|
||||
nb::class_<VisionBuf>(m, "VisionBuf")
|
||||
.def_prop_ro("data", [](VisionBuf &buf) { return Buffer(buf.addr, {buf.len}); }, nb::rv_policy::reference_internal,
|
||||
nb::sig("def data(self) -> memoryview"))
|
||||
.def_ro("width", &VisionBuf::width)
|
||||
.def_ro("height", &VisionBuf::height)
|
||||
.def_ro("stride", &VisionBuf::stride)
|
||||
.def_ro("uv_offset", &VisionBuf::uv_offset)
|
||||
.def_ro("idx", &VisionBuf::idx)
|
||||
.def_ro("fd", &VisionBuf::fd)
|
||||
.def_prop_ro("frame_id", &VisionBuf::get_frame_id);
|
||||
|
||||
nb::class_<VisionIpcServer>(m, "VisionIpcServer")
|
||||
.def(nb::init<std::string>(), nb::arg("name"))
|
||||
.def("create_buffers", &VisionIpcServer::create_buffers, nb::arg("tp"), nb::arg("num_buffers"), nb::arg("width"), nb::arg("height"))
|
||||
.def("create_buffers_with_sizes", &VisionIpcServer::create_buffers_with_sizes,
|
||||
nb::arg("tp"), nb::arg("num_buffers"), nb::arg("width"), nb::arg("height"), nb::arg("size"), nb::arg("stride"), nb::arg("uv_offset"))
|
||||
.def("send", [](VisionIpcServer &server, VisionStreamType tp, InputBuffer data,
|
||||
uint32_t frame_id, uint64_t timestamp_sof, uint64_t timestamp_eof) {
|
||||
VisionBuf *buf = server.get_buffer(tp);
|
||||
if (buf->len != data.size()) {
|
||||
PyErr_SetString(PyExc_AssertionError, "buffer size does not match frame size");
|
||||
throw nb::python_error();
|
||||
}
|
||||
// The old binding accepted strided byte views as well as contiguous data.
|
||||
auto *source = data.data();
|
||||
if (data.stride(0) == 1) {
|
||||
std::memcpy(buf->addr, source, data.size());
|
||||
} else {
|
||||
auto *target = static_cast<uint8_t *>(buf->addr);
|
||||
for (size_t i = 0; i < data.size(); ++i) target[i] = source[static_cast<int64_t>(i) * data.stride(0)];
|
||||
}
|
||||
buf->set_frame_id(frame_id);
|
||||
VisionIpcBufExtra extra = {frame_id, timestamp_sof, timestamp_eof, false};
|
||||
server.send(buf, &extra, false);
|
||||
}, nb::arg("tp"), nb::arg("data").noconvert(), nb::arg("frame_id") = 0, nb::arg("timestamp_sof") = 0, nb::arg("timestamp_eof") = 0,
|
||||
nb::sig("def send(self, tp: int, data: bytes | bytearray | memoryview, frame_id: int = 0, timestamp_sof: int = 0, timestamp_eof: int = 0) -> None"))
|
||||
.def("start_listener", &VisionIpcServer::start_listener);
|
||||
|
||||
nb::class_<PythonVisionIpcClient>(m, "VisionIpcClient")
|
||||
.def(nb::init<std::string, VisionStreamType, bool>(), nb::arg("name"), nb::arg("stream"), nb::arg("conflate"))
|
||||
.def_prop_ro("width", [](PythonVisionIpcClient &client) -> std::optional<size_t> {
|
||||
return client.num_buffers ? std::make_optional(client.buffers[0].width) : std::nullopt;
|
||||
})
|
||||
.def_prop_ro("height", [](PythonVisionIpcClient &client) -> std::optional<size_t> {
|
||||
return client.num_buffers ? std::make_optional(client.buffers[0].height) : std::nullopt;
|
||||
})
|
||||
.def_prop_ro("stride", [](PythonVisionIpcClient &client) -> std::optional<size_t> {
|
||||
return client.num_buffers ? std::make_optional(client.buffers[0].stride) : std::nullopt;
|
||||
})
|
||||
.def_prop_ro("uv_offset", [](PythonVisionIpcClient &client) -> std::optional<size_t> {
|
||||
return client.num_buffers ? std::make_optional(client.buffers[0].uv_offset) : std::nullopt;
|
||||
})
|
||||
.def_prop_ro("buffer_len", [](PythonVisionIpcClient &client) -> std::optional<size_t> {
|
||||
return client.num_buffers ? std::make_optional(client.buffers[0].len) : std::nullopt;
|
||||
})
|
||||
.def_prop_ro("num_buffers", [](PythonVisionIpcClient &client) { return client.num_buffers; })
|
||||
.def_prop_ro("frame_id", [](PythonVisionIpcClient &client) { return client.extra.frame_id; })
|
||||
.def_prop_ro("timestamp_sof", [](PythonVisionIpcClient &client) { return client.extra.timestamp_sof; })
|
||||
.def_prop_ro("timestamp_eof", [](PythonVisionIpcClient &client) { return client.extra.timestamp_eof; })
|
||||
.def_prop_ro("valid", [](PythonVisionIpcClient &client) { return client.extra.valid; })
|
||||
.def("recv", [](PythonVisionIpcClient &client, int timeout_ms) {
|
||||
nb::gil_scoped_release release;
|
||||
return client.recv(&client.extra, timeout_ms);
|
||||
}, nb::arg("timeout_ms") = 100, nb::rv_policy::reference_internal,
|
||||
nb::sig("def recv(self, timeout_ms: int = 100) -> VisionBuf | None"))
|
||||
// Holds the GIL: connect remaps the buffers other threads may be reading.
|
||||
.def("connect", [](PythonVisionIpcClient &client, bool blocking) {
|
||||
return client.connect(blocking);
|
||||
}, nb::arg("blocking"))
|
||||
.def("is_connected", [](PythonVisionIpcClient &client) { return client.is_connected(); })
|
||||
.def_static("available_streams", &VisionIpcClient::getAvailableStreams, nb::arg("name"), nb::arg("block"),
|
||||
nb::call_guard<nb::gil_scoped_release>());
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# distutils: language = c++
|
||||
#cython: language_level=3
|
||||
|
||||
from .visionipc cimport VisionBuf as cppVisionBuf
|
||||
|
||||
cdef class VisionBuf:
|
||||
cdef cppVisionBuf * buf
|
||||
|
||||
@staticmethod
|
||||
cdef create(cppVisionBuf*)
|
||||
@@ -0,0 +1,163 @@
|
||||
# distutils: language = c++
|
||||
# cython: c_string_encoding=ascii, language_level=3
|
||||
|
||||
from libc.string cimport memcpy
|
||||
from libc.stdint cimport uint32_t, uint64_t
|
||||
from libcpp cimport bool
|
||||
from libcpp.string cimport string
|
||||
|
||||
from .visionipc cimport VisionIpcServer as cppVisionIpcServer
|
||||
from .visionipc cimport VisionIpcClient as cppVisionIpcClient
|
||||
from .visionipc cimport VisionBuf as cppVisionBuf
|
||||
from .visionipc cimport VisionIpcBufExtra, VisionStreamType
|
||||
from .visionipc cimport get_endpoint_name as cpp_get_endpoint_name
|
||||
|
||||
|
||||
def get_endpoint_name(string name, VisionStreamType stream):
|
||||
return cpp_get_endpoint_name(name, stream).decode('utf-8')
|
||||
|
||||
|
||||
cdef class VisionBuf:
|
||||
@staticmethod
|
||||
cdef create(cppVisionBuf * cbuf):
|
||||
buf = VisionBuf()
|
||||
buf.buf = cbuf
|
||||
return buf
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
cdef unsigned char[:] data = <unsigned char[:self.buf.len]> self.buf.addr
|
||||
return memoryview(data)
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.buf.width
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.buf.height
|
||||
|
||||
@property
|
||||
def stride(self):
|
||||
return self.buf.stride
|
||||
|
||||
@property
|
||||
def uv_offset(self):
|
||||
return self.buf.uv_offset
|
||||
|
||||
@property
|
||||
def idx(self):
|
||||
return self.buf.idx
|
||||
|
||||
@property
|
||||
def fd(self):
|
||||
return self.buf.fd
|
||||
|
||||
@property
|
||||
def frame_id(self):
|
||||
return self.buf.get_frame_id()
|
||||
|
||||
|
||||
cdef class VisionIpcServer:
|
||||
cdef cppVisionIpcServer * server
|
||||
|
||||
def __init__(self, string name):
|
||||
self.server = new cppVisionIpcServer(name)
|
||||
|
||||
def create_buffers(self, VisionStreamType tp, size_t num_buffers, size_t width, size_t height):
|
||||
self.server.create_buffers(tp, num_buffers, width, height)
|
||||
|
||||
def create_buffers_with_sizes(self, VisionStreamType tp, size_t num_buffers, size_t width, size_t height, size_t size, size_t stride, size_t uv_offset):
|
||||
self.server.create_buffers_with_sizes(tp, num_buffers, width, height, size, stride, uv_offset)
|
||||
|
||||
def send(self, VisionStreamType tp, const unsigned char[:] data, uint32_t frame_id=0, uint64_t timestamp_sof=0, uint64_t timestamp_eof=0):
|
||||
cdef cppVisionBuf * buf = self.server.get_buffer(tp)
|
||||
|
||||
# Populate buffer
|
||||
assert buf.len == len(data)
|
||||
memcpy(buf.addr, &data[0], len(data))
|
||||
buf.set_frame_id(frame_id)
|
||||
|
||||
cdef VisionIpcBufExtra extra
|
||||
extra.frame_id = frame_id
|
||||
extra.timestamp_sof = timestamp_sof
|
||||
extra.timestamp_eof = timestamp_eof
|
||||
extra.valid = False
|
||||
|
||||
self.server.send(buf, &extra, False)
|
||||
|
||||
def start_listener(self):
|
||||
self.server.start_listener()
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.server
|
||||
|
||||
|
||||
cdef class VisionIpcClient:
|
||||
cdef cppVisionIpcClient * client
|
||||
cdef VisionIpcBufExtra extra
|
||||
|
||||
def __cinit__(self, string name, VisionStreamType stream, bool conflate):
|
||||
self.client = new cppVisionIpcClient(name, stream, conflate)
|
||||
|
||||
def __dealloc__(self):
|
||||
del self.client
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return self.client.buffers[0].width if self.client.num_buffers else None
|
||||
|
||||
@property
|
||||
def height(self):
|
||||
return self.client.buffers[0].height if self.client.num_buffers else None
|
||||
|
||||
@property
|
||||
def stride(self):
|
||||
return self.client.buffers[0].stride if self.client.num_buffers else None
|
||||
|
||||
@property
|
||||
def uv_offset(self):
|
||||
return self.client.buffers[0].uv_offset if self.client.num_buffers else None
|
||||
|
||||
@property
|
||||
def buffer_len(self):
|
||||
return self.client.buffers[0].len if self.client.num_buffers else None
|
||||
|
||||
@property
|
||||
def num_buffers(self):
|
||||
return self.client.num_buffers
|
||||
|
||||
@property
|
||||
def frame_id(self):
|
||||
return self.extra.frame_id
|
||||
|
||||
@property
|
||||
def timestamp_sof(self):
|
||||
return self.extra.timestamp_sof
|
||||
|
||||
@property
|
||||
def timestamp_eof(self):
|
||||
return self.extra.timestamp_eof
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return self.extra.valid
|
||||
|
||||
def recv(self, int timeout_ms=100):
|
||||
cdef cppVisionBuf * buf
|
||||
# release the GIL, this can block for timeout_ms
|
||||
with nogil:
|
||||
buf = self.client.recv(&self.extra, timeout_ms)
|
||||
if not buf:
|
||||
return None
|
||||
return VisionBuf.create(buf)
|
||||
|
||||
def connect(self, bool blocking):
|
||||
return self.client.connect(blocking)
|
||||
|
||||
def is_connected(self):
|
||||
return self.client.is_connected()
|
||||
|
||||
@staticmethod
|
||||
def available_streams(string name, bool block):
|
||||
return cppVisionIpcClient.getAvailableStreams(name, block)
|
||||
Executable
BIN
Binary file not shown.
+12
-12
@@ -1,5 +1,5 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=77", "nanobind>=2.12", "scons"]
|
||||
requires = ["setuptools>=77", "Cython>=3.1", "scons"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
@@ -21,14 +21,10 @@ dependencies = []
|
||||
Repository = "https://github.com/commaai/msgq"
|
||||
|
||||
[project.optional-dependencies]
|
||||
# for building from source with SCons
|
||||
build = [
|
||||
"nanobind>=2.12",
|
||||
"scons",
|
||||
]
|
||||
dev = [
|
||||
"msgq-ipc[build]",
|
||||
"setuptools", # for distutils
|
||||
"Cython",
|
||||
"scons",
|
||||
"ruff",
|
||||
"cppcheck",
|
||||
"cpplint",
|
||||
@@ -45,16 +41,13 @@ include = ["msgq", "msgq.*"]
|
||||
namespaces = false
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"msgq" = ["*.pyi", "py.typed", "*.h"]
|
||||
"msgq.visionipc" = ["*.pyi", "*.h"]
|
||||
"msgq" = ["*.pxd", "*.h"]
|
||||
"msgq.visionipc" = ["*.pxd", "*.h"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build = "cp311-* cp312-* cp313-* cp314-*"
|
||||
test-command = "python -m unittest discover -s msgq"
|
||||
|
||||
[tool.cibuildwheel.macos.environment]
|
||||
MACOSX_DEPLOYMENT_TARGET = "10.13"
|
||||
|
||||
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
|
||||
[tool.ruff]
|
||||
lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF100", "A"]
|
||||
@@ -63,3 +56,10 @@ lint.flake8-implicit-str-concat.allow-multiline=false
|
||||
|
||||
line-length = 160
|
||||
target-version="py311"
|
||||
|
||||
[tool.ty.src]
|
||||
exclude = ["site_scons/"]
|
||||
|
||||
[tool.ty.rules]
|
||||
# Cython modules are compiled at build time, not available for static analysis
|
||||
unresolved-import = "ignore"
|
||||
|
||||
+3
-7
@@ -2,7 +2,6 @@ import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
from setuptools import Extension, setup
|
||||
from setuptools.command.build_ext import build_ext
|
||||
@@ -12,19 +11,16 @@ class SConsBuildExt(build_ext):
|
||||
def build_extensions(self):
|
||||
subprocess.check_call([sys.executable, "-m", "SCons", "--minimal", "-j", str(self.parallel or os.cpu_count() or 1)])
|
||||
for ext in self.extensions:
|
||||
module = Path(*ext.name.split("."))
|
||||
source = module.with_suffix(sysconfig.get_config_var("EXT_SUFFIX"))
|
||||
source = Path(*ext.name.split(".")).with_suffix(".so")
|
||||
target = Path(self.get_ext_fullpath(ext.name))
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.copy_file(str(source), str(target))
|
||||
stub = module.with_suffix(".pyi")
|
||||
self.copy_file(str(stub), str(target.parent / stub.name))
|
||||
|
||||
|
||||
setup(
|
||||
ext_modules=[
|
||||
Extension("msgq._ipc", sources=[]),
|
||||
Extension("msgq.visionipc._visionipc", sources=[]),
|
||||
Extension("msgq.ipc_pyx", sources=[]),
|
||||
Extension("msgq.visionipc.visionipc_pyx", sources=[]),
|
||||
],
|
||||
cmdclass={"build_ext": SConsBuildExt},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import re
|
||||
import SCons
|
||||
from SCons.Action import Action
|
||||
from SCons.Scanner import Scanner
|
||||
|
||||
pyx_from_import_re = re.compile(r'^from\s+(\S+)\s+cimport', re.M)
|
||||
pyx_import_re = re.compile(r'^cimport\s+(\S+)', re.M)
|
||||
cdef_import_re = re.compile(r'^cdef extern from\s+.(\S+).:', re.M)
|
||||
|
||||
|
||||
def pyx_scan(node, env, path, arg=None):
|
||||
contents = node.get_text_contents()
|
||||
|
||||
# from <module> cimport ...
|
||||
matches = pyx_from_import_re.findall(contents)
|
||||
# cimport <module>
|
||||
matches += pyx_import_re.findall(contents)
|
||||
|
||||
# Modules can be either .pxd or .pyx files
|
||||
files = [m.replace('.', '/') + '.pxd' for m in matches]
|
||||
files += [m.replace('.', '/') + '.pyx' for m in matches]
|
||||
|
||||
# cdef extern from <file>
|
||||
files += cdef_import_re.findall(contents)
|
||||
|
||||
# Handle relative imports
|
||||
cur_dir = str(node.get_dir())
|
||||
files = [cur_dir + f if f.startswith('/') else f for f in files]
|
||||
|
||||
# Filter out non-existing files (probably system imports)
|
||||
files = [f for f in files if env.File(f).exists()]
|
||||
return env.File(files)
|
||||
|
||||
|
||||
pyxscanner = Scanner(function=pyx_scan, skeys=['.pyx', '.pxd'], recursive=True)
|
||||
cythonAction = Action("$CYTHONCOM")
|
||||
|
||||
|
||||
def create_builder(env):
|
||||
try:
|
||||
cython = env['BUILDERS']['Cython']
|
||||
except KeyError:
|
||||
cython = SCons.Builder.Builder(
|
||||
action=cythonAction,
|
||||
emitter={},
|
||||
suffix=cython_suffix_emitter,
|
||||
single_source=1
|
||||
)
|
||||
env.Append(SCANNERS=pyxscanner)
|
||||
env['BUILDERS']['Cython'] = cython
|
||||
return cython
|
||||
|
||||
def cython_suffix_emitter(env, source):
|
||||
return "$CYTHONCFILESUFFIX"
|
||||
|
||||
def generate(env):
|
||||
env["CYTHON"] = "cythonize"
|
||||
env["CYTHONCOM"] = "$CYTHON $CYTHONFLAGS $SOURCE"
|
||||
env["CYTHONCFILESUFFIX"] = ".cpp"
|
||||
|
||||
c_file, _ = SCons.Tool.createCFileBuilders(env)
|
||||
|
||||
c_file.suffix['.pyx'] = cython_suffix_emitter
|
||||
c_file.add_action('.pyx', cythonAction)
|
||||
|
||||
c_file.suffix['.py'] = cython_suffix_emitter
|
||||
c_file.add_action('.py', cythonAction)
|
||||
|
||||
create_builder(env)
|
||||
|
||||
def exists(env):
|
||||
return True
|
||||
+2
-2
@@ -18,8 +18,8 @@ lefthook run test
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
uv venv --python "$DIR/.venv/bin/python" "$TEST_DIR/.venv"
|
||||
uv pip install --python "$TEST_DIR/.venv/bin/python" "$DIR"
|
||||
cd "$("$TEST_DIR/.venv/bin/python" -c "import sysconfig; print(sysconfig.get_path('purelib'))")"
|
||||
"$TEST_DIR/.venv/bin/python" -m unittest discover -t . -s msgq
|
||||
cd "$TEST_DIR"
|
||||
"$TEST_DIR/.venv/bin/python" -m unittest msgq.tests.test_messaging
|
||||
)
|
||||
|
||||
# *** all done ***
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ body:
|
||||
attributes:
|
||||
label: Provide a route where the issue occurs
|
||||
description: Ensure the route is fully uploaded at https://useradmin.comma.ai. We cannot look into issues without routes, or at least a Dongle ID.
|
||||
placeholder: 77611a1fac303767/2020-05-11--16-37-07
|
||||
placeholder: 77611a1fac303767|2020-05-11--16-37-07
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ body:
|
||||
attributes:
|
||||
label: Provide a route where the issue occurs if applicable
|
||||
description: Ensure the route is fully uploaded at https://useradmin.comma.ai
|
||||
placeholder: 77611a1fac303767/2020-05-11--16-37-07
|
||||
placeholder: 77611a1fac303767|2020-05-11--16-37-07
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
|
||||
@@ -109,10 +109,6 @@ Start off by recording a route with lots of interesting events: enable LKAS and
|
||||
|
||||
Use the [longitudinal maneuvers](https://github.com/commaai/openpilot/tree/master/tools/longitudinal_maneuvers) report to evaluate your car's longitudinal control and tune it.
|
||||
|
||||
### Validation
|
||||
|
||||
Before submitting a port, go through the [car porting checklist](docs/CAR_PORTING_CHECKLIST.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
All opendbc development is coordinated on GitHub and [Discord](https://discord.comma.ai). Check out the `#dev-opendbc-cars` channel and `Vehicle Specific` section.
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
# Car porting checklist
|
||||
|
||||
This checklist covers common issues we find in new car ports and many of the things we check before merging them. Use it as a guide to help you write a high-quality port and get it reviewed faster.
|
||||
|
||||
Not everything will apply to your car. If something can't be tested, let us know what and why. Where it applies, check each supported variant and both stock ACC and openpilot longitudinal. Some checks need the full openpilot stack, not just opendbc. Field names like `accFaulted` are defined in [`car.capnp`](../opendbc/car/car.capnp).
|
||||
|
||||
## Vehicle and setup
|
||||
|
||||
- Vehicle details
|
||||
|
||||
- [ ] Record make, model, year, trim, market, and relevant driver assistance options in `values.py`.
|
||||
- If all variants in the US market come equipped with the necessary ADAS package, no package documentation is required.
|
||||
- If a vehicle varies in features in another market, add a new [`CarDocs`](../opendbc/car/docs_definitions.py) entry.
|
||||
- [ ] Add every observed ECU firmware version to `fingerprints.py`.
|
||||
- [ ] Identify differences from related supported cars; verify reused signals and commands on this variant.
|
||||
- Gas, gear, and brake signals often move between ICE, hybrid, and EV variants (e.g. Hyundai, Subaru, Toyota SecOC).
|
||||
|
||||
- Harness and CAN topology
|
||||
|
||||
- [ ] Confirm harness pinout, CAN bus assignments, and CAN/CAN FD configuration.
|
||||
- [ ] Identify which ECUs send, receive, and forward each relevant message.
|
||||
- This is useful to know which messages we need to forward and block in safety.
|
||||
|
||||
- Stock behavior and power lifecycle
|
||||
|
||||
- [ ] Record baseline stock LKAS, ACC, stop-and-go, and driver override behavior for reference when writing the port.
|
||||
- Stock behavior should be matched when possible, e.g. how stock ACC commands standstill and resume.
|
||||
- [ ] Verify ignition detection, startup, shutdown, and vehicle sleep with the device connected.
|
||||
|
||||
## Identification and parameters
|
||||
|
||||
- [ ] The car identifies correctly on repeated cold starts without a manually forced fingerprint or fingerprint cache.
|
||||
- [ ] [Firmware queries](../opendbc/car/fw_versions.py) use the correct ECU addresses and buses without causing any DTCs on the dashboard.
|
||||
- [ ] Exact and [fuzzy fingerprinting](../opendbc/car/fw_versions.py) distinguish variants that need different control or safety behavior.
|
||||
- [ ] Unknown or ambiguous firmware does not silently select an incompatible platform.
|
||||
- A vehicle variant we do not support that has identical firmware (such as lacking ACC or LKAS hardware) should be detected with a CAN signal in `interface.py` or `carstate.py` depending on what we can check, and set `dashcamOnly`.
|
||||
- [ ] Reliable source for wheelbase, steering ratio, and other vehicle model parameters.
|
||||
- [ ] `minSteerSpeed`, `minEnableSpeed`, `autoResumeSng`, and `steerAtStandstill` in `carParams` match observed behavior.
|
||||
|
||||
## CAN parsing and message construction
|
||||
|
||||
- Signal definitions and input validation
|
||||
|
||||
- [ ] Message addresses, lengths, buses, frequencies, endianness, signedness, scales, and units are verified.
|
||||
- [ ] Checksums and counters validate against real captures, including counter wraparound.
|
||||
- [ ] Missing, stale, malformed, or checksum-invalid messages produce the expected invalid CAN state.
|
||||
- [ ] Variable-rate or intermittent messages have an [explicit frequency](../opendbc/can/parser.py) in `get_can_parsers` so they don't cause false CAN errors.
|
||||
- For example, VW `Blinkmodi_02`, `HCA_01`, and `AWV_03` are 1 Hz when inactive and 50 Hz when active.
|
||||
- [ ] Startup defaults and invalid signal values cannot look like valid engagement conditions.
|
||||
|
||||
- Outgoing messages and forwarding
|
||||
|
||||
- [ ] Copied stock messages preserve unrelated fields and use fresh source data.
|
||||
- [ ] Transmitted messages have the correct bus, timing, counters, checksums, and request bits.
|
||||
- [ ] Forwarding and replacement rules prevent duplicate actuation messages or bus loops.
|
||||
|
||||
- Diagnostics
|
||||
|
||||
- [ ] Diagnostic sessions, [ECU disabling](../opendbc/car/disable_ecu.py), and keepalives work across startup, restart, and shutdown, if used.
|
||||
- This is for disabling an ECU, such as to gain longitudinal control. Some ports do not require this.
|
||||
|
||||
## Vehicle state: basics
|
||||
|
||||
- Seatbelt and doors
|
||||
|
||||
- [ ] Driver seatbelt: buckle and unbuckle both update `seatbeltUnlatched` correctly.
|
||||
- [ ] Doors: open and close each door individually; `doorOpen` remains true until all monitored doors close.
|
||||
|
||||
- Gears
|
||||
|
||||
- [ ] Gear selection: ideally we parse park, reverse, neutral, drive, and alternate drive modes such as sport, B (brake) gear on hybrids, etc.
|
||||
- If we cannot parse all gears, ensure we can disengage cruise on non-drive/sport gears, or anything that stock ACC would normally disengage for.
|
||||
- [ ] Unknown gear values are handled explicitly instead of defaulting to drive.
|
||||
|
||||
- Pedals and regen
|
||||
|
||||
- [ ] Brake pedal: light press, firm press, hold, and release update `brakePressed` reliably.
|
||||
- [ ] Accelerator pedal: press and release update `gasPressed`, including while cruise is active.
|
||||
- [ ] Driver pedal signals are distinguished from braking or acceleration commanded by ACC/openpilot.
|
||||
- In the past, we found some accelerator signals either activate when stock ACC accelerates, or don't activate when the user's gas was under the stock ACC request. `gasPressed` must be true whenever the user is pressing the gas pedal any amount, in any state.
|
||||
- [ ] Regen paddles or other driver regen controls update `regenBraking`, if applicable.
|
||||
|
||||
- Parking brake and auto hold
|
||||
|
||||
- [ ] Parking brake engagement and release update `parkingBrake`.
|
||||
- [ ] Auto hold engagement and release update `brakeHoldActive`, independently of the brake pedal.
|
||||
|
||||
- Speed and standstill
|
||||
|
||||
- [ ] Vehicle speed and individual wheel speeds have correct units and plausible values.
|
||||
- [ ] Speed and `standstill` use the same signals as the safety model.
|
||||
- For example, Nissan and GM use the rear wheel speeds to match safety.
|
||||
- [ ] `standstill` distinguishes a complete stop from slow creep and updates promptly on drive-off.
|
||||
- [ ] Cluster speed and cruise set speed agree with the car in both metric and imperial settings.
|
||||
|
||||
- Steering
|
||||
|
||||
- [ ] Steering angle has the correct sign, scale, center offset, and behavior through its full range.
|
||||
- [ ] Steering torque has the correct sign and scale; driver torque and EPS torque are not confused.
|
||||
- [ ] `steeringPressed` detects driver input in both directions without triggering on normal commanded steering.
|
||||
- Use [`CS.update_steering_pressed`](../opendbc/car/interfaces.py) if the signal is noisy.
|
||||
|
||||
- Blinkers and blind spots
|
||||
|
||||
- [ ] Left/right blinkers are reported correctly.
|
||||
- If we only have lamp state, use [`CS.update_blinker_from_lamp`](../opendbc/car/interfaces.py) so it doesn't oscillate with the blinker.
|
||||
- If we only have stalk position, use [`CS.update_blinker_from_stalk`](../opendbc/car/interfaces.py) so short taps are held.
|
||||
- [ ] Left/right blind spot indications are mapped correctly and clear when the stock indication clears.
|
||||
|
||||
- Faults and stock system state
|
||||
|
||||
- [ ] Temporary and permanent steering faults are logged correctly and recover only when the fault clears.
|
||||
- EPS initializing states at startup are not permanent faults (e.g. VW, BYD).
|
||||
- Expected low-speed steering lockouts should set `lowSpeedAlert` instead of a fault, usually with hysteresis.
|
||||
- [ ] `accFaulted`, `carFaultedNonCritical`, `carNotReady`, and `vehicleSensorsInvalid` are reported correctly.
|
||||
- [ ] ESP disabled/active states and stock AEB/FCW states are parsed where available.
|
||||
- [ ] Stock LKAS settings (`invalidLkasSetting`) and conventional/non-adaptive cruise modes (`cruiseState.nonAdaptive`) are detected where relevant.
|
||||
- For example, Nissan requires stock LKAS off, Mazda requires it on, and Tesla requires Autosteer off.
|
||||
|
||||
## Buttons and cruise state
|
||||
|
||||
- [ ] Set, resume, cancel, main cruise, speed up/down, gap, and LKAS buttons are mapped where present.
|
||||
- [ ] Button press and release edges each occur once; held buttons do not cause unintended repeated engagement.
|
||||
- [ ] Short presses, long presses, and simultaneous button presses are handled correctly.
|
||||
- [ ] Cruise available (main), enabled, standstill, and set speed are parsed properly and match stock behavior.
|
||||
- Cruise enabled should stay true during driver accelerator override and at standstill. Some dash signals fall in these states (e.g. Subaru).
|
||||
- `cruiseState.standstill` should only be true if the car requires a resume press to drive off.
|
||||
- [ ] Set speed changes, unit changes, and minimum/maximum set speeds behave correctly.
|
||||
|
||||
## Engagement and cancellation
|
||||
|
||||
- Engagement conditions
|
||||
|
||||
- [ ] The intended set/resume action engages once, only when the car is ready (`carNotReady`).
|
||||
- [ ] Engagement refusal and disengagement for seatbelt, doors, gear, parking brake, and faults match the intended policy.
|
||||
|
||||
- Cancellation
|
||||
|
||||
- [ ] The physical cancel button disengages openpilot and cancels stock cruise as intended.
|
||||
- [ ] If stock ACC is controlling longitudinal, verify our cancel message stops commanding acceleration.
|
||||
- [ ] Software cancellation works in each supported cruise state, including standstill and driver accelerator override.
|
||||
- [ ] Cancel wins over simultaneous set/resume requests; repeated cancel requests do not resume or re-enable cruise.
|
||||
- If the cancel button can toggle pause/resume or the main button can engage, use `blockPcmEnable` to only allow PCM enable after a real user button press (e.g. Hyundai).
|
||||
- [ ] Cruise main off disengages and prevents unintended re-engagement.
|
||||
|
||||
- Disengagement and recovery
|
||||
|
||||
- [ ] Releasing a pedal, closing a door, buckling a belt, or clearing a fault does not unexpectedly re-engage unless we are still in the soft-disable grace period.
|
||||
- [ ] Stock ACC disengagement/fault causes the expected openpilot response when using stock longitudinal control.
|
||||
- [ ] Lateral-only and longitudinal-active states set their respective actuation request bits correctly.
|
||||
- For example, `CC.enabled` should be used when keeping the car's cruise engaged during a longitudinal override while `CC.longActive` falls.
|
||||
|
||||
## Lateral control
|
||||
|
||||
- Commands, limits, and driver override
|
||||
|
||||
- [ ] Small left/right commands produce the expected steering direction.
|
||||
- [ ] Command magnitude and rate limits match the platform and safety implementation.
|
||||
- [ ] Driver override reduces or cancels openpilot steering assistance as intended, in both directions.
|
||||
- Some platforms disengage both steering and cruise on a steering override (e.g. Tesla); use `steeringDisengage` for this.
|
||||
|
||||
- Low-speed behavior
|
||||
|
||||
- [ ] Steering behaves correctly around the minimum steering speed, including crossing it in both directions.
|
||||
- [ ] Check for low-speed oscillation and flag it if present. One solution is to low-pass filter the request below a certain speed.
|
||||
|
||||
- Faults and recovery
|
||||
|
||||
- [ ] Sustained steering, high steering rates, and request-bit transitions do not cause unexpected EPS faults.
|
||||
- [ ] Temporary steering unavailability stops active requests and produces the expected user alert.
|
||||
|
||||
- Tuning and reported output
|
||||
|
||||
- [ ] Tuning is checked across representative speeds and curves for oscillation, lag, and saturation.
|
||||
- [ ] Reported `actuatorsOutput` matches the command actually sent after clipping and rate limiting.
|
||||
|
||||
## Longitudinal control
|
||||
|
||||
- Supported control modes
|
||||
|
||||
- [ ] Stock ACC works correctly with openpilot lateral control, including cancel, override, and stop-and-go.
|
||||
- There are two levels of support with openpilot-controlled longitudinal:
|
||||
- [ ] Alpha longitudinal is chosen if we cannot maintain stock AEB or other safety features, or the car does not have a radar we can parse. openpilot longitudinal is then only available on development branches.
|
||||
- [ ] openpilot longitudinal is the only mode and available in all branches if both of the above requirements are met, plus we have a good understanding of the car's longitudinal API.
|
||||
|
||||
- Acceleration, braking, and driver override
|
||||
|
||||
- [ ] Acceleration and braking command signs, scales, limits, and rate limits are verified.
|
||||
- [ ] Acceleration, coasting, and braking transitions are smooth and free of conflicting requests.
|
||||
- Some cars accept gas and brake commands separately; never request both at once.
|
||||
- [ ] Brake press disengages while moving as well as while at a standstill.
|
||||
- [ ] Accelerator override and release behave correctly, i.e. we stop requesting acceleration (inactive, or 0 m/s^2) while continuing to steer.
|
||||
|
||||
- Stopping, holding, and resuming
|
||||
|
||||
- [ ] Low-speed braking reaches a complete stop without unexpected creep or brake release.
|
||||
- [ ] Stop hold works for short and extended stops; timeout and parking brake behavior are understood.
|
||||
- [ ] Resume/drive-off releases hold in the correct sequence without unintended acceleration.
|
||||
- [ ] Resume is sent only when requested and supported.
|
||||
- [ ] Stops and starts on grades behave correctly without unexpected rollback or parking brake/gear transitions.
|
||||
- [ ] `stopAccel`, `vEgoStopping`, `vEgoStarting`, `startingState`, and `startAccel` are tuned if needed.
|
||||
|
||||
- Powertrain states and faults
|
||||
|
||||
- [ ] Behavior is checked across relevant powertrain states, such as engine stop/start.
|
||||
- [ ] ACC faults and transient not-ready states inhibit requests appropriately and recover cleanly.
|
||||
|
||||
- Tuning and stock safety features
|
||||
|
||||
- [ ] `longitudinalActuatorDelay` and tuning are evaluated across representative speeds, following, stops, and starts.
|
||||
- [ ] Stock AEB/FCW interaction and any feature loss from ECU disabling are understood and documented.
|
||||
|
||||
## Radar, alerts, and stock features
|
||||
|
||||
- Radar
|
||||
|
||||
- [ ] Radar distance, lateral position, relative velocity, signs, units, and track IDs are verified, if supported.
|
||||
- [ ] Radar track creation, updates, disappearance, invalid targets, and timeouts behave correctly.
|
||||
- [ ] `radarDelay` is set if known.
|
||||
|
||||
- HUD and alerts
|
||||
|
||||
- [ ] HUD engaged/standby state, set speed, lane lines, lead indication, and gap bars match the requested state.
|
||||
- [ ] Takeover, steering unavailable, and other required alerts appear and clear correctly.
|
||||
|
||||
- Stock feature interaction
|
||||
|
||||
- [ ] Stock LKAS and ACC do not fight openpilot commands.
|
||||
- [ ] Stock safety messages are forwarded or arbitrated as intended; validate AEB interactions with replay/bench tests.
|
||||
- [ ] Normal operation does not introduce persistent dash warnings or diagnostic trouble codes.
|
||||
- Some cars may display warning messages on the dash or force a disengage if they detect a conflict between what their camera is sending and our request through read-back signals from the PCM or EPS. If we need to spoof a message to the camera/ADAS ECU, we need to verify it does not affect stock safety features such as LKAS or AEB.
|
||||
|
||||
## Safety and failure handling
|
||||
|
||||
- Message permissions and engagement
|
||||
|
||||
- [ ] The selected safety mode permits only intended transmit addresses, buses, and message lengths.
|
||||
- [ ] Safety engagement/disengagement agrees with the car interface for cruise, buttons, pedals, and motion state.
|
||||
- [ ] Non-zero actuation or active request bits are rejected when safety's [`controls_allowed`](../opendbc/safety/safety.h) is false.
|
||||
- [ ] Any messages permitted while disengaged are limited to their intended inactive/cancel behavior.
|
||||
|
||||
- Actuation limits
|
||||
|
||||
- [ ] Steering magnitude, rate, driver torque, measured torque, and request-bit checks are covered as applicable.
|
||||
- [ ] Longitudinal limits, inactive values, and cancel/resume restrictions are covered as applicable.
|
||||
- [ ] All actuation-capable fields in transmitted messages are constrained, including fields unused by the controller.
|
||||
|
||||
- Communication failures and recovery
|
||||
|
||||
- [ ] Invalid counters/checksums, missing required input messages, and stale state disable control as intended.
|
||||
- [ ] Relay malfunction detection and forwarding restrictions cover the actual harness topology.
|
||||
|
||||
- Test coverage
|
||||
|
||||
- [ ] Safety tests cover every new safety flag variant and reject disallowed commands.
|
||||
|
||||
## Regression tests
|
||||
|
||||
- Routes and automated checks
|
||||
|
||||
- [ ] Add representative routes to the [car test routes](../opendbc/car/tests/routes.py), covering relevant variants and modes.
|
||||
- [ ] Add or update tests for fingerprinting, parsing, command construction, and discovered edge cases.
|
||||
- [ ] Run applicable [car interface tests](../opendbc/car/tests/) and [safety tests](../opendbc/safety/tests/).
|
||||
- [ ] [`test_models.py`](../opendbc/car/tests/test_models.py) passes on the new routes; it replays them to check CAN validity and that safety agrees with carstate and accepts our commands.
|
||||
- [ ] Run the repository checks in [`test.sh`](../test.sh) and fix any failures.
|
||||
|
||||
- Regression validation
|
||||
|
||||
- [ ] Recheck related existing cars affected by shared DBC, controller, interface, or safety changes.
|
||||
- [ ] Repeat startup, engagement, cancellation, and shutdown over multiple drives and ignition cycles.
|
||||
Binary file not shown.
@@ -146,7 +146,6 @@ class CANParser:
|
||||
self.can_invalid_cnt: int = CAN_INVALID_CNT
|
||||
self.last_nonempty_nanos: int = 0
|
||||
self._last_update_nanos: int = 0
|
||||
self._last_invalid_log_nanos: int | None = None
|
||||
|
||||
def _add_message(self, name_or_addr: str | int, freq: float | None = None) -> None:
|
||||
if isinstance(name_or_addr, numbers.Number):
|
||||
@@ -202,20 +201,13 @@ class CANParser:
|
||||
valid = True
|
||||
counters_valid = True
|
||||
bus_timeout = self.bus_timeout
|
||||
invalid_messages = set()
|
||||
for state in self.message_states.values():
|
||||
if state.counter_fail >= MAX_BAD_COUNTER:
|
||||
counters_valid = False
|
||||
state.rate_limited_log(self._last_update_nanos, f"counter invalid, {state.counter_fail=} {MAX_BAD_COUNTER=}")
|
||||
if not state.valid(self._last_update_nanos, bus_timeout):
|
||||
valid = False
|
||||
invalid_messages.add(state.address)
|
||||
|
||||
if invalid_messages and (self._last_invalid_log_nanos is None or
|
||||
self._last_update_nanos - self._last_invalid_log_nanos >= 10_000_000_000):
|
||||
messages = ', '.join(f"{hex(addr)} {self.message_states[addr].name}" for addr in sorted(invalid_messages))
|
||||
carlog.warning(f"CANParser: bus {self.bus} not valid (timeout or missing): {messages}")
|
||||
self._last_invalid_log_nanos = self._last_update_nanos
|
||||
state.rate_limited_log(self._last_update_nanos, "not valid (timeout or missing)")
|
||||
|
||||
# TODO: probably only want to increment this once per update() call
|
||||
self.can_invalid_cnt = 0 if valid else min(self.can_invalid_cnt + 1, CAN_INVALID_CNT)
|
||||
|
||||
@@ -360,7 +360,6 @@ class CarController(CarControllerBase):
|
||||
self.volt_cc_cancel_pending = False
|
||||
self.volt_cc_last_cancel_frame = 0
|
||||
self.volt_cc_metric = False
|
||||
self.volt_cc_speed_ceiling_mps = None
|
||||
self.volt_cc_last_button_frame = 0
|
||||
self.volt_cc_last_direction_frame = None
|
||||
self.volt_cc_consumed_source_ns = 0
|
||||
@@ -1397,10 +1396,7 @@ class CarController(CarControllerBase):
|
||||
self.volt_cc_consumed_source_ns = source_ns
|
||||
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, button))
|
||||
elif self.frame % 4 == 0:
|
||||
cruise_kph = CS.out.vCruise
|
||||
if self.volt_cc_speed_ceiling_mps is not None:
|
||||
cruise_kph = min(cruise_kph, self.volt_cc_speed_ceiling_mps * CV.MS_TO_KPH)
|
||||
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, cruise_kph,
|
||||
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, CS.out.vCruise,
|
||||
bool(CC.hudControl.leadVisible), self.volt_cc_metric)
|
||||
source_ns = physical.button_credit_ns
|
||||
if ((self.frame - self.volt_cc_last_button_frame) * DT_CTRL > rate and
|
||||
|
||||
@@ -335,8 +335,8 @@ def test_actual_card_active_observation_requires_fresh_selfdrive_state(monkeypat
|
||||
host.observe_ioniq6_long_authority = lambda *args: None
|
||||
host.update_vehicle_state_context = lambda *args: None
|
||||
|
||||
def update_cruise_speed(_state, _pending, *, wheel_context=None):
|
||||
assert wheel_context is None
|
||||
def update_cruise_speed(_state, _pending, *, approved=None):
|
||||
assert approved is None
|
||||
host.update_cruise_speed = update_cruise_speed
|
||||
host.v_cruise_helper = SimpleNamespace(slc_consumed_button=None, toyota=None)
|
||||
host.startup_panda_configured = lambda: True
|
||||
|
||||
@@ -8,9 +8,6 @@ class AngleTxRecovery:
|
||||
self.clock = time.monotonic_ns if clock is None else clock
|
||||
self.address = address
|
||||
self.bus = bus
|
||||
self.length = 24 if address == 0xCB else 32
|
||||
self.active_index = 3 if address == 0xCB else 9
|
||||
self.active_mask = 0xF if address == 0xCB else 3
|
||||
self.sent = deque(maxlen=4)
|
||||
self.last_packet_ns = 0
|
||||
self.pending_frame = -1
|
||||
@@ -19,7 +16,7 @@ class AngleTxRecovery:
|
||||
def record(self, frames, frame):
|
||||
now_ns = self.clock()
|
||||
for address, data, bus in frames:
|
||||
if address == self.address and bus == self.bus and len(data) == self.length and (data[self.active_index] >> 4 & self.active_mask) == 2:
|
||||
if address == self.address and bus == self.bus and len(data) == 32 and (data[9] >> 4 & 3) == 2:
|
||||
self.sent.append((frame, now_ns, bytes(data)))
|
||||
|
||||
def observe(self, packets, frame):
|
||||
@@ -29,7 +26,7 @@ class AngleTxRecovery:
|
||||
continue
|
||||
self.last_packet_ns = stamp
|
||||
for address, data, bus in frames:
|
||||
if address != self.address or bus != self.bus + 0xC0 or len(data) != self.length:
|
||||
if address != self.address or bus != self.bus + 0xC0 or len(data) != 32:
|
||||
continue
|
||||
# Consume an exact recent command once. The original local send clock
|
||||
# also bounds delayed echoes when controller updates have been paused.
|
||||
|
||||
@@ -96,12 +96,9 @@ def qualified(cp, *, marked_only=False):
|
||||
|
||||
|
||||
SPORTAGE_LONG_AOL_WORDS = frozenset(0x0804 | fuel | topology for fuel in (0, 2) for topology in (0, 8, 16, 144, 32, 40))
|
||||
CARNIVAL_LONG_AOL_WORD = 0x28B4
|
||||
LONG_AOL_WORDS = frozenset((CARNIVAL_LONG_AOL_WORD, 0x080D, 0x0815, 0x0895, 0x2815, 0x2895)) | SPORTAGE_LONG_AOL_WORDS
|
||||
LONG_AOL_WORDS = frozenset((0x080D, 0x0815, 0x0895, 0x2815, 0x2895)) | SPORTAGE_LONG_AOL_WORDS
|
||||
LONG_EV_CARS = frozenset((CAR.HYUNDAI_IONIQ_5, CAR.HYUNDAI_KONA_EV_2ND_GEN))
|
||||
INDEPENDENT_LONG_BUTTON_CARS = LONG_EV_CARS | {
|
||||
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.KIA_CARNIVAL_2025,
|
||||
}
|
||||
INDEPENDENT_LONG_BUTTON_CARS = LONG_EV_CARS | {CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN}
|
||||
|
||||
|
||||
def qualified_long(cp, *, marked_only=False):
|
||||
@@ -121,9 +118,6 @@ def qualified_long(cp, *, marked_only=False):
|
||||
elif cp.carFingerprint == CAR.KIA_SPORTAGE_5TH_GEN:
|
||||
if word is None or word | 0x804 not in SPORTAGE_LONG_AOL_WORDS:
|
||||
return False
|
||||
elif cp.carFingerprint == CAR.KIA_CARNIVAL_2025:
|
||||
if word != 0x20B0:
|
||||
return False
|
||||
elif word not in (0x11, 0x91):
|
||||
return False
|
||||
raw = int(cp.safetyConfigs[config_index(cp)].safetyParam)
|
||||
|
||||
@@ -24,7 +24,6 @@ from opendbc.car.hyundai import hyundaicanfd, hyundaican
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus
|
||||
from opendbc.car.hyundai.ray_pedal import create_ray_pedal_command, ray_pedal_gas, ray_pedal_enabled
|
||||
from opendbc.car.hyundai.ioniq6_longitudinal import Ioniq6LongitudinalPolicy
|
||||
from opendbc.car.hyundai.ioniq6_bsm import BlindspotStatus
|
||||
from opendbc.car.hyundai.canfd_dash_icons import CanFDDashIcons
|
||||
from opendbc.car.hyundai.classic_scc_aol import qualified as qualified_classic_stock_aol
|
||||
from opendbc.car.hyundai.classic_long_aol import AOL_EXPERIENCE, qualified as qualified_classic_long_aol
|
||||
@@ -72,7 +71,7 @@ def angle_torque_reduction_gain(steering_torque, v_ego, lat_active, last_gain):
|
||||
bp4 = np.interp(v_ego, [2.0, 22.0], [400.0, 700.0])
|
||||
target = np.interp(abs(steering_torque), [bp1, bp2, bp3, bp4], [ceiling, shelf, shelf, floor])
|
||||
else:
|
||||
return 0.0
|
||||
target = 0.0
|
||||
return round(rate_limit(target, last_gain, -0.014, 0.004) / 0.004) * 0.004
|
||||
|
||||
|
||||
@@ -118,8 +117,7 @@ class CarController(CarControllerBase):
|
||||
self.angle_vm = VehicleModel(CP) if CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING else None
|
||||
self.apply_angle_last = 0.0
|
||||
self.angle_gain_last = 0.0
|
||||
self.angle_tx_recovery = (AngleTxRecovery(0xCB, self.CAN.ECAN)
|
||||
if pe_long_qualified(CP) or ev9_long_qualified(CP) else AngleTxRecovery(0x110, self.CAN.ACAN)
|
||||
self.angle_tx_recovery = (AngleTxRecovery(0x110, self.CAN.ACAN)
|
||||
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9) and
|
||||
(qualified_angle_aol(CP, marked_only=True) or qualified_ccnc_ev_stock(CP)) else None)
|
||||
|
||||
@@ -298,7 +296,6 @@ class CarController(CarControllerBase):
|
||||
self.frame += 1
|
||||
if _projection is not None:
|
||||
_projection.finish(new_actuators.torque)
|
||||
can_sends.extend(CS.daw.command(now_nanos))
|
||||
return new_actuators, can_sends
|
||||
|
||||
def create_can_msgs(self, apply_steer_req, apply_torque, torque_fault, set_speed_in_units, accel, stopping,
|
||||
@@ -438,12 +435,6 @@ class CarController(CarControllerBase):
|
||||
if ev9_long:
|
||||
drive = CS.out.gearShifter == structs.CarState.GearShifter.drive
|
||||
measured = CS.angle_steering_angle
|
||||
if self.angle_tx_recovery is not None and self.angle_tx_recovery.take_rejection(self.frame):
|
||||
self.apply_angle_last = float(np.clip(measured, -self.params.ANGLE_LIMITS.STEER_ANGLE_MAX,
|
||||
self.params.ANGLE_LIMITS.STEER_ANGLE_MAX))
|
||||
self.angle_gain_last = 0.0
|
||||
if self.ev9_angle_filter is not None:
|
||||
self.ev9_angle_filter.x = self.apply_angle_last
|
||||
safety_speed = max(CS.out.vEgoRaw - 1.0, 1.0)
|
||||
bound = min(get_max_angle_vm(safety_speed, self.ev9_legacy_envelope, self.params),
|
||||
get_max_angle_vm(safety_speed, self.angle_vm, self.params))
|
||||
@@ -519,9 +510,8 @@ class CarController(CarControllerBase):
|
||||
)
|
||||
else:
|
||||
steering_status_active = CC.enabled
|
||||
if ((self.CP.carFingerprint == CAR.KIA_CARNIVAL_2025 and self.CP.safetyConfigs[-1].safetyParam == 0x28B4) or
|
||||
self.ev6_aol_status or (self.ioniq6_longitudinal is not None and
|
||||
self.CP.safetyConfigs[-1].safetyParam in (0x8815, 0x8895))):
|
||||
if self.ev6_aol_status or (self.ioniq6_longitudinal is not None and
|
||||
self.CP.safetyConfigs[-1].safetyParam in (0x8815, 0x8895)):
|
||||
# AOL may steer while ordinary cruise engagement remains off.
|
||||
steering_status_active |= CC.latActive
|
||||
can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN,
|
||||
@@ -597,9 +587,6 @@ class CarController(CarControllerBase):
|
||||
can_now_ns = getattr(CS, "ioniq6_bsm_now_ns", 0)
|
||||
status = (sources.status(can_now_ns) if sources is not None and can_now_ns > self.ioniq6_bsm_last_can_ns else None)
|
||||
if status is not None:
|
||||
# Neutralize only the injected dashboard indication below 5 mph.
|
||||
if CS.out.vEgoRaw < 5 * CV.MPH_TO_MS:
|
||||
status = BlindspotStatus(0, 0)
|
||||
can_sends.extend(hyundaicanfd.create_ioniq6_blindspot_status(self.ioniq6_bsm_counter, status))
|
||||
self.ioniq6_bsm_counter = (self.ioniq6_bsm_counter + 1) & 0xff
|
||||
self.ioniq6_bsm_last_can_ns = can_now_ns
|
||||
|
||||
@@ -62,8 +62,6 @@ def get_non_scc_cruise_signals(flags: int, car=None) -> tuple[str, str, str, str
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP):
|
||||
super().__init__(CP)
|
||||
from opendbc.car.hyundai.daw import DawSource
|
||||
self.daw = DawSource(CP)
|
||||
self.forte_lkas_sources = NonSccLkasSources() if qualified_non_scc(CP) and CP.flags & HyundaiFlags.HAS_LDA_BUTTON else None
|
||||
if (qualified_classic_scc(CP) or qualified_classic_long(CP)) and CP.flags & HyundaiFlags.HAS_LDA_BUTTON:
|
||||
self.forte_lkas_sources = ClassicSccLkasSources(CP.carFingerprint)
|
||||
@@ -133,6 +131,7 @@ class CarState(CarStateBase):
|
||||
self.ccnc_1b5_ts_ns = 0
|
||||
self.dashboard_limit = LimitTracker()
|
||||
self.dashboard_stop_sign = None
|
||||
self.drive_mode = None
|
||||
self.ioniq6_bsm_sources = (Ioniq6BlindspotSources()
|
||||
if CP.carFingerprint == CAR.HYUNDAI_IONIQ_6 and CP.openpilotLongitudinalControl else None)
|
||||
self.ioniq6_bsm_now_ns = 0
|
||||
@@ -610,6 +609,10 @@ class CarState(CarStateBase):
|
||||
stamp = cp_cam.ts_nanos["ADAS_0x380"]["STOP_SIGN"]
|
||||
self.dashboard_stop_sign = (bool(cp_cam.vl["ADAS_0x380"]["STOP_SIGN"]), stamp) if stamp > 0 else None
|
||||
|
||||
if self.CP.flags & HyundaiFlags.EV:
|
||||
stamp = cp.ts_nanos["DRIVE_MODE_EV"]["DRIVE_MODE"]
|
||||
self.drive_mode = (int(cp.vl["DRIVE_MODE_EV"]["DRIVE_MODE"]), stamp) if stamp > 0 else None
|
||||
|
||||
return ret
|
||||
|
||||
def get_can_parsers_canfd(self, CP):
|
||||
@@ -624,6 +627,7 @@ class CarState(CarStateBase):
|
||||
msgs.extend((("ADAS_CMD_50_50ms", math.nan), ("BLINDSPOTS_FRONT_CORNER_2", math.nan)))
|
||||
if CP.flags & HyundaiFlags.EV:
|
||||
msgs.append(("MANUAL_SPEED_LIMIT_ASSIST", math.nan))
|
||||
msgs.append(("DRIVE_MODE_EV", math.nan))
|
||||
sign_subscription = ("FR_CMR_02_100ms", math.nan)
|
||||
if CP.flags & HyundaiFlags.CANFD_LKA_STEER_MSG:
|
||||
msgs.append(sign_subscription)
|
||||
@@ -669,12 +673,6 @@ class CarState(CarStateBase):
|
||||
msgs.append(("CRUISE_BUTTONS_ALT", math.nan))
|
||||
if not (ev9_long_qualified(CP) or pe_long_qualified(CP)):
|
||||
(cam_msgs if CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else msgs).append(("SCC_CONTROL", 50))
|
||||
elif CP.carFingerprint == CAR.KIA_CARNIVAL_2025 and CP.openpilotLongitudinalControl and CP.safetyConfigs[-1].safetyParam in (0x20B4, 0x28B4):
|
||||
msgs = [(name, frequency) for name, frequency in msgs if name != "CRUISE_BUTTONS_ALT"]
|
||||
msgs.extend((("CRUISE_BUTTONS_ALT", 50), ("ACCELERATOR_BRAKE_ALT", 100), ("TCS", 50),
|
||||
("WHEEL_SPEEDS", 100), ("MDPS", 100), (self.gear_msg_canfd, 100),
|
||||
("STEERING_SENSORS", 100), ("DOORS_SEATBELTS", 10), ("BLINKERS", 10), ("SCC_CONTROL", math.nan)))
|
||||
cam_msgs.append(("CAM_0x362", 20))
|
||||
elif torque_ev_scc_eligible(CP):
|
||||
msgs.extend(((self.accelerator_msg_canfd, 100), ("TCS", 50), ("WHEEL_SPEEDS", 100), ("MDPS", 100)))
|
||||
msgs.extend((("CRUISE_BUTTONS_ALT", math.nan), ("DOORS_SEATBELTS", math.nan),
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
from opendbc.car import CanData
|
||||
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags, is_blended
|
||||
|
||||
|
||||
CANFD_DAW_WORDS = frozenset((0x11, 0x91, 0x811, 0x891, 0x8015, 0x8095, 0x8815, 0x8895))
|
||||
|
||||
|
||||
class DawSource:
|
||||
def __init__(self, cp):
|
||||
self.canfd = bool(cp.flags & HyundaiFlags.CANFD)
|
||||
self.enabled = (cp.brand == 'hyundai' and not cp.passive and not cp.dashcamOnly and not cp.notCar and
|
||||
not is_blended(cp) and (not self.canfd or cp.carFingerprint == CAR.HYUNDAI_IONIQ_6))
|
||||
if self.canfd:
|
||||
self.enabled = bool(self.enabled and cp.safetyConfigs and
|
||||
int(cp.safetyConfigs[-1].safetyParam) in CANFD_DAW_WORDS and CanBus(cp).ECAN % 4 == 1)
|
||||
self.address = 0x11A if self.canfd else 0x53E
|
||||
self.source_bus = CanBus(cp).ECAN if self.canfd else 2
|
||||
self.target_bus = CanBus(cp).ECAN if self.canfd else 0
|
||||
self.length = 16 if self.canfd else 6
|
||||
self.data = None
|
||||
self.stamp = 0
|
||||
|
||||
def update(self, packets):
|
||||
if not self.enabled:
|
||||
return
|
||||
for stamp, frames in packets:
|
||||
for address, data, bus in frames:
|
||||
if address != self.address or bus != self.source_bus:
|
||||
continue
|
||||
if stamp <= self.stamp or len(data) != self.length or (self.canfd and
|
||||
int.from_bytes(data[:2], 'little') != hkg_can_fd_checksum(address, None, data)):
|
||||
self.data = None
|
||||
continue
|
||||
self.data, self.stamp = bytes(data), stamp
|
||||
|
||||
def command(self, now_ns):
|
||||
if (not self.enabled or self.source_bus == self.target_bus or self.data is None or
|
||||
not 0 < self.stamp <= now_ns <= self.stamp + 100_000_000):
|
||||
return []
|
||||
data = bytearray(self.data)
|
||||
if self.canfd:
|
||||
if (data[7] >> 3) & 7 == 1:
|
||||
data[7] &= ~0x38
|
||||
data[:2] = hkg_can_fd_checksum(self.address, None, data).to_bytes(2, 'little')
|
||||
elif 1 <= data[5] & 7 <= 3:
|
||||
data[5] &= ~7
|
||||
return [CanData(self.address, bytes(data), self.target_bus)]
|
||||
@@ -314,7 +314,7 @@ def qualified(cp):
|
||||
cp.alternativeExperience not in (0, 32) or
|
||||
not cp.alphaLongitudinalAvailable or len(cp.safetyConfigs) != 1 or cp.safetyConfigs[0].safetyParam != LONG_PARAM):
|
||||
return False
|
||||
if type(cp) in (_DynamicStructBuilder, _DynamicStructReader) and cp.schema == structs.CarParams.schema:
|
||||
if type(cp) is _DynamicStructBuilder and cp.schema == structs.CarParams.schema:
|
||||
return metadata_qualified(cp)
|
||||
stock = stock_copy(cp)
|
||||
stock.alternativeExperience = 0
|
||||
|
||||
@@ -49,11 +49,6 @@ EXISTING_OWNERS_OR_DISTINCT_RECEIVERS = frozenset((
|
||||
CAR.GENESIS_GV80,
|
||||
))
|
||||
|
||||
def carnival_long(cp):
|
||||
return (cp.carFingerprint == CAR.KIA_CARNIVAL_2025 and qualified_long(cp) and
|
||||
cp.safetyConfigs[0].safetyParam in (0x20B4, 0x28B4))
|
||||
|
||||
|
||||
STOCK_WORDS = (0x10, 0x11, 0x12, 0x90, 0x91, 0x92)
|
||||
|
||||
|
||||
@@ -70,8 +65,6 @@ def qualified(cp):
|
||||
HyundaiFlags.CANFD_ALT_GEARS | HyundaiFlags.CANFD_ALT_GEARS_2)
|
||||
if not required(cp) or cp.carFingerprint not in CARS:
|
||||
return False
|
||||
if carnival_long(cp):
|
||||
return cp.alphaLongitudinalAvailable and cp.alternativeExperience == 0
|
||||
factory = CAR(cp.carFingerprint)
|
||||
stock = stock_copy(cp)
|
||||
return (cp.alphaLongitudinalAvailable and cp.alternativeExperience == 0 and not cp.pcmCruise and
|
||||
@@ -98,13 +91,13 @@ class GenericHDA2Startup(RadarHDA2Startup):
|
||||
raise RuntimeError('Ordinary HDA-II finalization requires configured startup')
|
||||
if self.prepared_for(ci.CP):
|
||||
return
|
||||
long_aol = self.outcome in (Outcome.PENDING, Outcome.SENT_UNCONFIRMED) and qualified_long(ci.CP, marked_only=True)
|
||||
long_aol = self.outcome is Outcome.SENT_UNCONFIRMED and qualified_long(ci.CP, marked_only=True)
|
||||
if not long_aol and (self.outcome not in (Outcome.STOCK_UNTOUCHED, Outcome.STOCK_RESTORED) or
|
||||
not stock_qualified(ci.CP, marked_only=True)):
|
||||
raise RuntimeError('Ordinary HDA-II startup does not admit this AOL configuration')
|
||||
expected: Any = deepcopy(self.prepared_cp)
|
||||
configs = expected.get('safetyConfigs', [])
|
||||
allowed_words = (*tuple(word | 4 for word in STOCK_WORDS), 0x20B4) if long_aol else (*STOCK_WORDS, 0x20B0)
|
||||
allowed_words = tuple(word | 4 for word in STOCK_WORDS) if long_aol else STOCK_WORDS
|
||||
if (len(configs) != 1 or configs[0]['safetyModel'] != 'hyundaiCanfd' or
|
||||
configs[0]['safetyParam'] not in allowed_words or expected['alternativeExperience'] != 0):
|
||||
raise RuntimeError('Ordinary HDA-II prepared stock profile cannot be finalized')
|
||||
@@ -114,50 +107,3 @@ class GenericHDA2Startup(RadarHDA2Startup):
|
||||
if ci.CP.to_dict() != expected:
|
||||
raise RuntimeError('Ordinary HDA-II finalization changed unrelated CarParams')
|
||||
self.prepared_cp = expected
|
||||
|
||||
def prepare_when_controls_ready(self, *, admission):
|
||||
if not carnival_long(self.cp):
|
||||
return self.prepare(admission=admission)
|
||||
if self.outcome is not None or self.closed:
|
||||
raise RuntimeError('Carnival startup preparation is single-use')
|
||||
if admission() is not True:
|
||||
return self.prepare(admission=lambda: False)
|
||||
self.admission = admission
|
||||
self.floor_ns = time.clock_gettime_ns(time.CLOCK_BOOTTIME)
|
||||
self.outcome = Outcome.PENDING
|
||||
self.prepared_cp = deepcopy(self.cp.to_dict())
|
||||
return self.cp
|
||||
|
||||
def commit_pending(self, ci, *, sources_current, control_current):
|
||||
if (not carnival_long(ci.CP) or self.outcome is not Outcome.PENDING or self.closed or not self.published or
|
||||
ci is not self.ci or not self.prepared_for(ci.CP) or not sources_current or not control_current or
|
||||
self.admission is None or self.admission() is not True):
|
||||
raise RuntimeError('Carnival takeover requires current diagnostic, source and inactive control admission')
|
||||
try:
|
||||
success = self._disable(self.callbacks[0], self._send, bus=self.bus, addr=self.address,
|
||||
com_cont_req=self.communication_control_request())
|
||||
if not success or not self.disable_attempted:
|
||||
raise RuntimeError('Carnival pending ECU disable was not sent successfully')
|
||||
self.outcome = Outcome.SENT_UNCONFIRMED
|
||||
self.floor_ns = time.clock_gettime_ns(time.CLOCK_BOOTTIME)
|
||||
from opendbc.car import make_tester_present_msg
|
||||
self.keeper = self.keeper_factory(
|
||||
lambda: self.callbacks[1]([make_tester_present_msg(self.address, self.bus, suppress_response=True)]), clock=self.clock)
|
||||
self.keeper.start()
|
||||
except BaseException:
|
||||
if self.keeper is not None:
|
||||
self.keeper.stop()
|
||||
try:
|
||||
restored = not (self.session_attempted or self.disable_attempted) or self._restore()
|
||||
except Exception:
|
||||
restored = False
|
||||
self.outcome = Outcome.ABORT_UNCERTAIN
|
||||
if not restored:
|
||||
raise RuntimeError('Carnival pending takeover failed; stock restoration unverified') from None
|
||||
raise
|
||||
|
||||
def before_control(self, **context):
|
||||
if self.outcome is Outcome.PENDING:
|
||||
self.check()
|
||||
return False
|
||||
return super().before_control(**context)
|
||||
|
||||
@@ -285,17 +285,6 @@ def create_ioniq6_blindspot_status(counter: int, status: BlindspotStatus) -> lis
|
||||
def create_ccnc(packer, CAN, openpilot_longitudinal, enabled, hud, left_blinker, right_blinker, msg_161, msg_162, msg_1b5,
|
||||
is_metric, out, main_cruise_enabled, lfa_icon):
|
||||
if lfa_icon:
|
||||
# The host owns lateral; keep unrelated faults and obstruction warnings.
|
||||
for field in ("FAULT_LSS", "FAULT_HDA", "FAULT_DAS", "FAULT_LFA", "FAULT_DAW"):
|
||||
if msg_162.get(field) == 1:
|
||||
msg_162[field] = 0
|
||||
if msg_161.get("ALERTS_2") == 5:
|
||||
msg_161.update({"ALERTS_2": 0, "SOUNDS_2": 0})
|
||||
if msg_161.get("ALERTS_3") == 17:
|
||||
msg_161["ALERTS_3"] = 0
|
||||
if msg_161.get("ALERTS_5") in (2, 5):
|
||||
msg_161["ALERTS_5"] = 0
|
||||
msg_161["DAW_ICON"] = 0
|
||||
lane_change_speed_min = 8.9408
|
||||
any_blinker = left_blinker or right_blinker
|
||||
curvature = {i: (31 if i == -1 else 13 - abs(i + 15)) if i < 0 else 15 + i for i in range(-15, 16)}
|
||||
|
||||
@@ -396,9 +396,6 @@ class CarInterface(CarInterfaceBase):
|
||||
return ret
|
||||
|
||||
def update(self, can_packets):
|
||||
if self.CS.daw.enabled:
|
||||
can_packets = list(can_packets)
|
||||
self.CS.daw.update(can_packets)
|
||||
recovery = getattr(getattr(self, 'CC', None), 'angle_tx_recovery', None)
|
||||
if recovery is not None:
|
||||
can_packets = list(can_packets)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from math import isfinite
|
||||
from capnp.lib.capnp import _DynamicStructBuilder, _DynamicStructReader
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
from opendbc.car.hyundai.ccnc_ev_stock import qualified as stock_qualified, metadata_qualified
|
||||
from opendbc.car.hyundai.ccnc_ev_stock import qualified as stock_qualified
|
||||
from opendbc.car.hyundai.ev9_longitudinal import copy_cp
|
||||
|
||||
STOCK_PARAM = 0x5491
|
||||
@@ -37,17 +36,13 @@ def qualified(cp):
|
||||
if (not required(cp) or cp.pcmCruise or not cp.alphaLongitudinalAvailable or
|
||||
cp.alternativeExperience not in (0, 32) or len(cp.safetyConfigs) != 1 or
|
||||
cp.safetyConfigs[0].safetyParam != LONG_PARAM or
|
||||
cp.steerControlType != structs.CarParams.SteerControlType.angle):
|
||||
return False
|
||||
deprecated = cp.deprecated
|
||||
if (not deprecated.startingState or not all(isfinite(value) for value in (deprecated.startAccel, deprecated.vEgoStarting, deprecated.vEgoStopping,
|
||||
deprecated.stoppingDecelRate, cp.longitudinalActuatorDelay)) or
|
||||
abs(deprecated.startAccel - 1.) > 1e-6 or abs(deprecated.vEgoStarting - .4) > 1e-6 or
|
||||
abs(deprecated.vEgoStopping - .3) > 1e-6 or abs(deprecated.stoppingDecelRate - .4) > 1e-6 or
|
||||
cp.steerControlType != structs.CarParams.SteerControlType.angle or not cp.deprecated.startingState or
|
||||
not all(isfinite(value) for value in (cp.deprecated.startAccel, cp.deprecated.vEgoStarting, cp.deprecated.vEgoStopping,
|
||||
cp.deprecated.stoppingDecelRate, cp.longitudinalActuatorDelay)) or
|
||||
abs(cp.deprecated.startAccel - 1.) > 1e-6 or abs(cp.deprecated.vEgoStarting - .4) > 1e-6 or
|
||||
abs(cp.deprecated.vEgoStopping - .3) > 1e-6 or abs(cp.deprecated.stoppingDecelRate - .4) > 1e-6 or
|
||||
abs(cp.longitudinalActuatorDelay - .35) > 1e-6):
|
||||
return False
|
||||
if type(cp) in (_DynamicStructBuilder, _DynamicStructReader) and cp.schema == structs.CarParams.schema:
|
||||
return metadata_qualified(cp)
|
||||
stock = copy_cp(cp)
|
||||
stock.alternativeExperience = 0
|
||||
stock.openpilotLongitudinalControl = False
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.angle_tx_recovery import AngleTxRecovery
|
||||
from opendbc.car.hyundai.carcontroller import angle_torque_reduction_gain
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.tests.test_ev9_long_controller import packets
|
||||
from opendbc.car.hyundai.tests.test_stock_angle_aol import source_profile
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
|
||||
|
||||
@pytest.mark.parametrize('gain', (0.18, 0.532, 0.692, 0.848))
|
||||
def test_inactive_steering_does_not_retain_pre_fault_gain(gain):
|
||||
released = angle_torque_reduction_gain(0, 5., False, gain)
|
||||
assert released == 0., 'INACTIVE_STEERING_RETAINED_GAIN'
|
||||
assert angle_torque_reduction_gain(0, 5., True, released) == 0.004
|
||||
|
||||
|
||||
def profile(identity, long, aol):
|
||||
cp = source_profile(identity)
|
||||
if long:
|
||||
if identity == CAR.HYUNDAI_IONIQ_5_PE:
|
||||
from opendbc.car.hyundai.ioniq5pe_long_profile import candidate
|
||||
else:
|
||||
from opendbc.car.hyundai.ev9_longitudinal import candidate
|
||||
cp = candidate(cp, enabled=True, is_release=False)
|
||||
cp.alternativeExperience = 32 if aol else 0
|
||||
elif aol:
|
||||
cp.safetyConfigs[0].safetyParam |= 0x800 if not cp.safetyConfigs[0].safetyParam & 0x4000 else 0
|
||||
cp.alternativeExperience = 32
|
||||
return cp
|
||||
|
||||
|
||||
def controller(identity, long, aol):
|
||||
cp = profile(identity, long, aol)
|
||||
ci = CarInterface(cp)
|
||||
# Isolate command recovery after the separately tested owner handoff.
|
||||
ci.CC.pe_startup = SimpleNamespace(handed_off=True, can_emit=lambda controller: True)
|
||||
packer = CANPacker(ci.can_parsers[next(iter(ci.can_parsers))].dbc.name)
|
||||
for tick in range(1, 14):
|
||||
frames = packets(packer, tick, advance_counter=True)
|
||||
if not long and tick % 2 == 0:
|
||||
frames.append(packer.make_can_msg('SCC_CONTROL', 1, {'COUNTER': tick // 2, 'ACCMode': 1, 'MainMode_ACC': 1}))
|
||||
ci.update([(tick * 10_000_000, frames)])
|
||||
assert ci.CS.out.canValid
|
||||
return ci, packer
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
|
||||
@pytest.mark.parametrize('long', (False, True))
|
||||
@pytest.mark.parametrize('aol', (False, True))
|
||||
def test_actual_controller_fault_and_full_lateral_withdrawal_restart_gain_from_zero(identity, long, aol):
|
||||
ci, _ = controller(identity, long, aol)
|
||||
cs = ci.CS
|
||||
cs.out.vEgoRaw = cs.out.vEgo = 5.
|
||||
cs.out.steeringAngleDeg = cs.angle_steering_angle = 20.
|
||||
command = structs.CarControl(enabled=True, latActive=True)
|
||||
command.actuators.steeringAngleDeg = 50.
|
||||
ci.CC.apply_angle_last = 20.
|
||||
ci.CC.angle_gain_last = .692
|
||||
cs.out.steerFaultTemporary = cs.angle_steering_fault = True
|
||||
ci.CC.frame = 1
|
||||
_, inactive = ci.apply(command.as_reader(), 200_000_000)
|
||||
assert ci.CC.angle_gain_last == 0., 'FAULT_RETAINED_PREVIOUS_GAIN'
|
||||
cs.out.steerFaultTemporary = cs.angle_steering_fault = False
|
||||
ci.CC.frame = 3
|
||||
_, output = ci.apply(command.as_reader(), 210_000_000)
|
||||
assert ci.CC.angle_gain_last == .004
|
||||
address = 0xCB if long else 0x110
|
||||
payload = next(data for addr, data, _ in output if addr == address)
|
||||
assert payload[6 if long else 12] == 1
|
||||
command.latActive = command.enabled = False
|
||||
ci.CC.frame = 7
|
||||
ci.apply(command.as_reader(), 220_000_000)
|
||||
assert ci.CC.angle_gain_last == 0.
|
||||
|
||||
|
||||
@pytest.mark.parametrize('case', ('current','accepted','wrong_bus','wrong_address','wrong_payload','short','stale','duplicate','inactive'))
|
||||
def test_long_echo_exact_source_freshness_and_one_use(case):
|
||||
now = [1_000_000_000]
|
||||
recovery = AngleTxRecovery(0xCB, 1, clock=lambda: now[0])
|
||||
raw = bytearray(24)
|
||||
raw[3] = 0x10 if case == 'inactive' else 0x20
|
||||
recovery.record([(0xCB, bytes(raw), 1)], 10)
|
||||
now[0] += 31_000_000 if case == 'stale' else 10_000_000
|
||||
if case == 'wrong_payload':
|
||||
raw[6] ^= 1
|
||||
data = bytes(raw[:23] if case == 'short' else raw)
|
||||
bus = 129 if case == 'accepted' else 192 if case == 'wrong_bus' else 193
|
||||
address = 0x110 if case == 'wrong_address' else 0xCB
|
||||
recovery.observe([(now[0], [(address, data, bus)])], 11)
|
||||
assert recovery.take_rejection(11) == (case in ('current','duplicate'))
|
||||
assert not recovery.take_rejection(11)
|
||||
recovery.observe([(now[0]+1, [(address, data, bus)])], 12)
|
||||
assert not recovery.take_rejection(12)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
|
||||
def test_long_controller_consumes_current_rejection_without_retaining_filtered_history(identity):
|
||||
now = [1_000_000_000]
|
||||
with patch('time.monotonic_ns', side_effect=lambda: now[0]):
|
||||
ci, packer = controller(identity, True, True)
|
||||
assert ci.CC.angle_tx_recovery is not None, 'LONG_REJECTION_RECOVERY_MISSING'
|
||||
cs = ci.CS
|
||||
cs.out.vEgo = cs.out.vEgoRaw = 5.
|
||||
cs.out.steeringAngleDeg = cs.angle_steering_angle = -20.
|
||||
command = structs.CarControl(enabled=True, latActive=True)
|
||||
command.actuators.steeringAngleDeg = -100.
|
||||
ci.CC.apply_angle_last = -70.
|
||||
ci.CC.ev9_angle_filter.x = -70.
|
||||
ci.CC.angle_gain_last = .692
|
||||
ci.CC.frame = 21
|
||||
_, frames = ci.apply(command.as_reader(), now[0])
|
||||
emitted = next(frame for frame in frames if frame[0] == 0xCB)
|
||||
now[0] += 10_000_000
|
||||
ci.update([(now[0], [(emitted[0], emitted[1], 193)])])
|
||||
# Current physical angle is the new native limiter baseline after rejection.
|
||||
cs.out.canValid, cs.out.canTimeout = True, False
|
||||
cs.out.steeringAngleDeg = cs.angle_steering_angle = -20.
|
||||
_, frames = ci.apply(command.as_reader(), now[0])
|
||||
assert abs(ci.CC.apply_angle_last + 20.) <= 5.
|
||||
assert ci.CC.angle_gain_last == .004
|
||||
@@ -1,215 +0,0 @@
|
||||
"""Keep factory SCC until an inactive producer is current; never bypass native RX."""
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import Bus, structs
|
||||
from opendbc.car.hyundai.ecu_startup import Outcome
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.radar_interface import RadarInterface
|
||||
from opendbc.car.hyundai.tests.test_torque_ev_startup import Keeper
|
||||
from opendbc.car.hyundai.tests.test_generic_hda2_startup_lifecycle import physical_frames as source_frames
|
||||
from opendbc.car.hyundai.tests.test_ccnc_six import params
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified_long
|
||||
from opendbc.car.hyundai.torque_ev_scc import eligible as ev_scc_eligible
|
||||
from opendbc.safety.tests.libsafety import libsafety_py
|
||||
from openpilot.cereal import messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.prefix import OpenpilotPrefix
|
||||
from openpilot.selfdrive.car.card import Car
|
||||
from openpilot.starpilot.vehicle_startup import VehicleStartupOwner
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scenario", (
|
||||
"healthy_suppressed", "healthy_positive", "stale_control", "active_control", "missing_source",
|
||||
"native_mode", "cruise_fault", "stock_engaged", "disable_negative", "disable_malformed", "restore_failure", "publisher_failure"))
|
||||
def test_actual_card_defers_carnival_until_initialized_and_then_native_rx(scenario):
|
||||
stock = params(CAR.KIA_CARNIVAL_2025, hda2=True, alt_buttons=True, alpha=True, adas=True, alt_lka=True)
|
||||
assert stock.safetyConfigs[0].safetyParam == 0x20B4 and qualified_long(stock) and not ev_scc_eligible(stock)
|
||||
native = libsafety_py.libsafety
|
||||
assert native.set_safety_hooks(int(structs.CarParams.SafetyModel.elm327), 1) == 0
|
||||
native.init_tests()
|
||||
now = [100_000_000_000]
|
||||
requests, emitted = [], []
|
||||
muted = [False]
|
||||
|
||||
def send(frames):
|
||||
for address, data, bus in frames:
|
||||
assert native.safety_tx_hook(libsafety_py.make_CANPacket(address, bus, data))
|
||||
emitted.extend(frames)
|
||||
if scenario == "publisher_failure" and any(data[:4] == b"\x03\x28\x83\x01" for _, data, _ in frames):
|
||||
muted[0] = True
|
||||
raise OSError("diagnostic publisher failed after possible ECU delivery")
|
||||
|
||||
def query(sender, receiver, bus, addresses, commands, responses):
|
||||
command = commands[0]
|
||||
requests.append((now[0], command))
|
||||
|
||||
class Query:
|
||||
def get_data(self, timeout):
|
||||
sender([(0x730, bytes([len(command)]) + command + bytes(7 - len(command)), bus)])
|
||||
if command == b"\x28\x83\x01":
|
||||
muted[0] = True
|
||||
if scenario in ("disable_negative", "restore_failure"):
|
||||
return {(0x730, None): b"\x7f\x28\x22"}
|
||||
if scenario == "disable_malformed":
|
||||
return {(0x730, None): b"\x68"}
|
||||
return {(0x730, None): b"\x68\x03"} if scenario == "healthy_positive" else {}
|
||||
if command == b"\x28\x00\x01":
|
||||
if scenario == "restore_failure":
|
||||
return {}
|
||||
muted[0] = False
|
||||
return {(0x730, None): b""}
|
||||
return Query()
|
||||
|
||||
def sleep(seconds):
|
||||
now[0] += round(seconds * 1e9)
|
||||
|
||||
def update_sm(host, command, stale=False):
|
||||
message = messaging.new_message("carControl", valid=True, logMonoTime=now[0] - (151_000_000 if stale else 0))
|
||||
message.carControl = command
|
||||
panda = messaging.new_message("pandaStates", 1, valid=True, logMonoTime=now[0])
|
||||
state = panda.pandaStates[0]
|
||||
state.safetyModel, state.safetyParam = native.get_current_safety_mode(), native.get_current_safety_param()
|
||||
state.alternativeExperience = host.CP.alternativeExperience
|
||||
state.ignitionLine = True
|
||||
state.safetyRxChecksInvalid = native.safety_test_toyota_rx_summary_invalid()
|
||||
host.sm.update_msgs(now[0] / 1e9, [message.as_reader(), panda.as_reader()])
|
||||
|
||||
with OpenpilotPrefix(), patch.dict(os.environ, {"SIMULATION": "0", "AOL_REPLAY_RUNTIME": "1"}), \
|
||||
patch("time.clock_gettime_ns", side_effect=lambda clock: now[0]), \
|
||||
patch("time.monotonic_ns", side_effect=lambda: now[0]), \
|
||||
patch("time.monotonic", side_effect=lambda: now[0] / 1e9), \
|
||||
patch("time.sleep", side_effect=sleep):
|
||||
saved = Params()
|
||||
for key in ("OpenpilotEnabledToggle", "AlwaysOnLateral", "AlphaLongitudinalEnabled"):
|
||||
saved.put_bool(key, True, block=True)
|
||||
holder = VehicleStartupOwner()
|
||||
prepared = holder.prepare(stock, CarInterface, (list, send), requested=True,
|
||||
admission=lambda: True,
|
||||
defer_until_controls=True)
|
||||
startup = holder.owner
|
||||
assert startup is not None
|
||||
startup.clock, startup.keeper_factory, startup._query = lambda: now[0] / 1e9, Keeper, query
|
||||
try:
|
||||
assert startup.outcome is Outcome.PENDING and not requests and not emitted
|
||||
assert not startup.before_control(configured=True, sources_current=True, control_current=True)
|
||||
card = Car(CI=CarInterface(prepared), RI=RadarInterface(prepared), startup_owner=startup)
|
||||
startup.admission = card.startup_diagnostic_admission
|
||||
card.params.put_bool("IsOffroad", False, block=True)
|
||||
original_cp = card.CP.to_dict()
|
||||
assert card.CP.safetyConfigs[0].safetyParam == 0x28B4 and card.CP.alternativeExperience == 32
|
||||
assert not ev_scc_eligible(card.CP)
|
||||
# Explicit synthetic delayed producer: stock has not been told to stop.
|
||||
for tick in range(1, 701):
|
||||
now[0] += 10_000_000
|
||||
state = card.CI.update([(now[0], [f for f in source_frames(card.CI, tick) if f[0] not in (0x1A0, 0x1CF, 0x160)])])
|
||||
assert state.canValid and not state.canTimeout and not state.accFaulted
|
||||
assert not requests and not muted[0] and startup.keeper is None
|
||||
command = structs.CarControl(enabled=scenario == "active_control", latActive=scenario == "active_control").as_reader()
|
||||
if scenario == "missing_source":
|
||||
parser = card.CI.can_parsers[Bus.pt]
|
||||
parser.message_states[parser.dbc.name_to_msg["TCS"].address].timestamps.clear()
|
||||
if scenario == "cruise_fault":
|
||||
state.accFaulted = True
|
||||
if scenario == "stock_engaged":
|
||||
state.cruiseState.enabled = True
|
||||
config = card.CP.safetyConfigs[-1]
|
||||
if scenario == "native_mode":
|
||||
native.set_alternative_experience(card.CP.alternativeExperience)
|
||||
assert native.set_safety_hooks(int(config.safetyModel.raw), int(config.safetyParam)) == 0
|
||||
update_sm(card, command, stale=scenario == "stale_control")
|
||||
card.controls_update(state, command, initialize_only=True)
|
||||
assert not requests and not muted[0] and not card.ci_initialized
|
||||
if scenario in ("disable_negative", "disable_malformed", "restore_failure", "publisher_failure"):
|
||||
with pytest.raises(RuntimeError, match="pending"):
|
||||
card.controls_update(state, command)
|
||||
assert startup.outcome is Outcome.ABORT_UNCERTAIN and not card.ci_initialized
|
||||
assert card.CP.to_dict() == original_cp and not startup.handed_off
|
||||
assert muted[0] == (scenario == "restore_failure")
|
||||
assert startup.restored == (scenario != "restore_failure")
|
||||
return
|
||||
card.controls_update(state, command)
|
||||
if not scenario.startswith("healthy"):
|
||||
assert not requests and not muted[0] and not card.ci_initialized
|
||||
assert startup.outcome is Outcome.PENDING and card.CP.to_dict() == original_cp
|
||||
return
|
||||
assert [command for _, command in requests] == [b"\x10\x03", b"\x28\x83\x01"]
|
||||
assert muted[0] and card.ci_initialized and startup.outcome is Outcome.SENT_UNCONFIRMED
|
||||
assert startup.keeper.started and not startup.handed_off
|
||||
# No SCC while native is diagnostic, mismatched, or awaiting RX health.
|
||||
emitted.clear()
|
||||
card.controls_update(state, command)
|
||||
assert not emitted and not startup.handed_off
|
||||
native.set_alternative_experience(card.CP.alternativeExperience)
|
||||
assert native.set_safety_hooks(int(config.safetyModel.raw), int(config.safetyParam)) == 0
|
||||
native.init_tests()
|
||||
update_sm(card, command)
|
||||
card.controls_update(state, command)
|
||||
assert not emitted and not startup.handed_off
|
||||
for tick in range(701, 811):
|
||||
now[0] += 10_000_000
|
||||
frames = [f for f in source_frames(card.CI, tick) if f[0] not in (0x1A0, 0x1CF, 0x160)]
|
||||
state = card.CI.update([(now[0], frames)])
|
||||
native.set_timer(tick * 10_000)
|
||||
for address, data, bus in frames:
|
||||
native.safety_rx_hook(libsafety_py.make_CANPacket(address, bus, data))
|
||||
if tick % 100 == 0:
|
||||
native.safety_tick()
|
||||
assert native.safety_config_valid()
|
||||
update_sm(card, command)
|
||||
captured = []
|
||||
|
||||
def publish(frames, valid=True):
|
||||
if valid:
|
||||
send(frames)
|
||||
card.vehicle_startup.sent(frames, valid=valid)
|
||||
captured.extend(frames)
|
||||
|
||||
with patch.object(card, "publish_sendcan", side_effect=publish):
|
||||
card.controls_update(state, command)
|
||||
assert any(address == 0x1A0 and bus == 1 for address, _, bus in captured)
|
||||
decoder = card.CI.can_parsers[Bus.pt]
|
||||
decoder.update((now[0], captured))
|
||||
scc = decoder.vl['SCC_CONTROL']
|
||||
assert scc['ACCMode'] == 0 and scc['aReqRaw'] == 0 and scc['aReqValue'] == 0
|
||||
assert startup.handed_off and not startup.keeper.started
|
||||
assert not native.get_controls_allowed() and native.aol_get_permission_mask() == 0
|
||||
assert card.CP.to_dict() == original_cp
|
||||
finally:
|
||||
holder.close()
|
||||
|
||||
|
||||
def test_refused_takeover_preserves_exact_stock_aol_and_sends_no_disable():
|
||||
from opendbc.car.hyundai.ioniq6_handoff import TimestampedCanPacket
|
||||
from opendbc.car.hyundai.generic_hda2_startup import GenericHDA2Startup
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified as stock_qualified
|
||||
|
||||
cp = params(CAR.KIA_CARNIVAL_2025, hda2=True, alt_buttons=True, alpha=True, adas=True, alt_lka=True)
|
||||
now, tick, emitted = [100_000_000_000], [0], []
|
||||
startup = None
|
||||
|
||||
def receive():
|
||||
tick[0] += 1
|
||||
now[0] += 10_000_000
|
||||
return [TimestampedCanPacket(source_frames(startup.ci, tick[0]), now[0])]
|
||||
|
||||
with patch('time.clock_gettime_ns', side_effect=lambda clock: now[0]), \
|
||||
patch('time.monotonic', side_effect=lambda: now[0] / 1e9), patch('time.sleep'):
|
||||
startup = GenericHDA2Startup(cp, (receive, emitted.extend), clock=lambda: now[0] / 1e9, keeper_factory=Keeper)
|
||||
prepared = startup.prepare_when_controls_ready(admission=lambda: False)
|
||||
assert not prepared.openpilotLongitudinalControl and prepared.pcmCruise and not emitted
|
||||
ci = CarInterface(prepared)
|
||||
try:
|
||||
startup.configure(ci)
|
||||
assert startup.ready and ci.CS.out.canValid and startup.sources_current(ci, now[0])
|
||||
ci.CP.safetyConfigs[0].safetyParam |= 0x800
|
||||
assert ci.CP.safetyConfigs[0].safetyParam == 0x28B0 and stock_qualified(ci.CP, marked_only=True)
|
||||
startup.finalize_aol_configuration(ci)
|
||||
startup.seal_publication()
|
||||
assert startup.prepared_for(ci.CP) and not emitted
|
||||
assert startup.outcome is Outcome.STOCK_UNTOUCHED and startup.keeper is None
|
||||
finally:
|
||||
startup.close()
|
||||
@@ -1,51 +0,0 @@
|
||||
import pytest
|
||||
from opendbc.car import Bus
|
||||
from opendbc.car.hyundai.tests.test_ccnc_six import params
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
from opendbc.car.hyundai.canfd_stock_aol import qualified_long
|
||||
from opendbc.car.hyundai.generic_hda2_startup import qualified
|
||||
from opendbc.car.hyundai.carstate import CarState
|
||||
|
||||
|
||||
def carnival():
|
||||
return params(CAR.KIA_CARNIVAL_2025, hda2=True, alt_buttons=True, alpha=True, adas=True, alt_lka=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('word,experience,expected', [
|
||||
(0x20B4,0,True), (0x28B4,32,True), (0x28B4,0,False), (0x20B4,32,False),
|
||||
(0x28B6,32,False), (0x28B0,32,False), (0x8B4,32,False), (0x28B5,32,False),
|
||||
])
|
||||
def test_exact_profile(word, experience, expected):
|
||||
cp = carnival()
|
||||
cp.safetyConfigs[0].safetyParam, cp.alternativeExperience = word, experience
|
||||
assert qualified_long(cp) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mutation', ['hybrid','other_car','passive','dashcam','wrong_model','angle','other_bus'])
|
||||
def test_reject_other_configuration(mutation):
|
||||
cp = carnival()
|
||||
if mutation == 'hybrid':
|
||||
cp.flags = int(cp.flags | HyundaiFlags.HYBRID)
|
||||
elif mutation == 'other_car':
|
||||
cp.carFingerprint = CAR.KIA_CARNIVAL_HEV_4TH_GEN
|
||||
elif mutation == 'passive':
|
||||
cp.passive = True
|
||||
elif mutation == 'dashcam':
|
||||
cp.dashcamOnly = True
|
||||
elif mutation == 'angle':
|
||||
cp.flags = int(cp.flags | HyundaiFlags.CANFD_ANGLE_STEERING)
|
||||
elif mutation == 'other_bus':
|
||||
cp.safetyConfigs = [dict(safetyModel='noOutput'),dict(safetyModel='hyundaiCanfd',safetyParam=0x20B4)]
|
||||
else:
|
||||
cp.safetyConfigs[0].safetyModel = 'noOutput'
|
||||
assert not qualified(cp)
|
||||
|
||||
|
||||
def test_takeover_health_binds_physical_sources_not_removed_scc():
|
||||
cp = carnival()
|
||||
parser = CarState(cp).get_can_parsers(cp)[Bus.pt]
|
||||
assert parser.message_states[0x1AA].frequency == 50
|
||||
assert 0x1CF not in parser.addresses
|
||||
assert not parser.message_states[0x1A0].frequency > 0
|
||||
for address in (0x100,0x175,0xA0,0xEA):
|
||||
assert parser.message_states[address].frequency > 0
|
||||
@@ -1,72 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car.hyundai import ev9_longitudinal, ioniq5pe_long_profile
|
||||
from opendbc.car.hyundai.tests.test_stock_angle_aol import source_profile
|
||||
from opendbc.car.hyundai.values import CAR, HyundaiFlags
|
||||
|
||||
|
||||
def profile(identity):
|
||||
module = ioniq5pe_long_profile if identity == CAR.HYUNDAI_IONIQ_5_PE else ev9_longitudinal
|
||||
cp = module.candidate(source_profile(identity), enabled=True, is_release=False)
|
||||
return module, cp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
|
||||
@pytest.mark.parametrize('reader', (False, True))
|
||||
def test_canonical_long_validation_does_not_clone_carparams(identity, reader):
|
||||
module, cp = profile(identity)
|
||||
cp.alternativeExperience = 32
|
||||
value = cp.as_reader() if reader else cp
|
||||
before = cp.to_dict()
|
||||
with patch.object(module, 'copy_cp', side_effect=AssertionError('CYCLIC_LONG_QUALIFICATION_CLONED_CP')):
|
||||
assert module.qualified(value)
|
||||
cp.passive = True
|
||||
assert not module.qualified(value)
|
||||
cp.passive = False
|
||||
assert module.qualified(value)
|
||||
assert cp.to_dict() == before
|
||||
|
||||
|
||||
@pytest.mark.parametrize('identity', (CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9))
|
||||
@pytest.mark.parametrize('mutation', ('passive','dashcam','notcar','brand','fuel','long','pcm','experience','available','word','model','slots'))
|
||||
def test_every_admission_field_is_still_checked_on_every_call(identity, mutation):
|
||||
module, cp = profile(identity)
|
||||
assert module.qualified(cp)
|
||||
if mutation == 'passive':
|
||||
cp.passive = True
|
||||
elif mutation == 'dashcam':
|
||||
cp.dashcamOnly = True
|
||||
elif mutation == 'notcar':
|
||||
cp.notCar = True
|
||||
elif mutation == 'brand':
|
||||
cp.brand = 'toyota'
|
||||
elif mutation == 'fuel':
|
||||
cp.flags = int(cp.flags | HyundaiFlags.HYBRID)
|
||||
elif mutation == 'long':
|
||||
cp.openpilotLongitudinalControl = False
|
||||
elif mutation == 'pcm':
|
||||
cp.pcmCruise = True
|
||||
elif mutation == 'experience':
|
||||
cp.alternativeExperience = 33
|
||||
elif mutation == 'available':
|
||||
cp.alphaLongitudinalAvailable = False
|
||||
elif mutation == 'word':
|
||||
cp.safetyConfigs[0].safetyParam ^= 4
|
||||
elif mutation == 'model':
|
||||
cp.safetyConfigs[0].safetyModel = 'noOutput'
|
||||
else:
|
||||
cp.safetyConfigs = [cp.safetyConfigs[0].to_dict(), cp.safetyConfigs[0].to_dict()]
|
||||
assert not module.qualified(cp)
|
||||
assert not module.qualified(cp.as_reader())
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', [('startingState',False),('startAccel',.5),('vEgoStarting',.5),('vEgoStopping',.4),
|
||||
('stoppingDecelRate',.5),('startAccel',float('nan')),('startAccel',float('inf'))])
|
||||
def test_pe_frozen_longitudinal_values_still_refuse_mutation(field, value):
|
||||
module, cp = profile(CAR.HYUNDAI_IONIQ_5_PE)
|
||||
assert module.qualified(cp)
|
||||
setattr(cp.deprecated, field, value)
|
||||
assert not module.qualified(cp)
|
||||
assert not module.qualified(cp.as_reader())
|
||||
@@ -131,7 +131,7 @@ class TestCcncSix(unittest.TestCase):
|
||||
safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, cp.safetyConfigs[-1].safetyParam)
|
||||
safety.init_tests()
|
||||
bus = can.ECAN
|
||||
source_values: dict[str, float] = {"COUNTER": 17, "SET_ME_1": 1, "CRUISE_BUTTONS": 0, "BYTE6": 0x55}
|
||||
source_values = {"COUNTER": 17, "SET_ME_1": 1, "CRUISE_BUTTONS": 0, "BYTE6": 0x55}
|
||||
addr, raw, _ = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, source_values)
|
||||
data = bytearray(raw)
|
||||
data[0:2] = hkg_can_fd_checksum(addr, None, data).to_bytes(2, "little")
|
||||
@@ -163,12 +163,6 @@ class TestCcncSix(unittest.TestCase):
|
||||
active_source[0:2] = hkg_can_fd_checksum(addr, None, active_source).to_bytes(2, "little")
|
||||
self.assertTrue(safety.safety_rx_hook(libsafety_py.make_CANPacket(addr, bus, active_source)))
|
||||
self.assertFalse(safety.safety_tx_hook(tx))
|
||||
source["COUNTER"] = source_values["COUNTER"] = 19
|
||||
_, raw, _ = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, source_values)
|
||||
data = bytearray(raw)
|
||||
data[0:2] = hkg_can_fd_checksum(addr, None, data).to_bytes(2, "little")
|
||||
resume = create_carnival_alt_resume(packer, cp, can, source)
|
||||
tx = libsafety_py.make_CANPacket(resume[0], resume[2], resume[1])
|
||||
self.assertTrue(safety.safety_rx_hook(libsafety_py.make_CANPacket(addr, bus, bytes(data))))
|
||||
wrong_counter = bytearray(resume[1])
|
||||
wrong_counter[2] += 1
|
||||
@@ -183,8 +177,8 @@ class TestCcncSix(unittest.TestCase):
|
||||
safety.set_timer(0)
|
||||
self.assertTrue(safety.safety_tx_hook(tx))
|
||||
|
||||
source["COUNTER"] = 20
|
||||
source_values["COUNTER"] = 20
|
||||
source["COUNTER"] = 19
|
||||
source_values["COUNTER"] = 19
|
||||
source_values["ADAPTIVE_CRUISE_MAIN_BTN"] = 1
|
||||
_, raw, _ = packer.make_can_msg("CRUISE_BUTTONS_ALT", bus, source_values)
|
||||
data = bytearray(raw)
|
||||
@@ -232,37 +226,3 @@ class TestCcncSix(unittest.TestCase):
|
||||
wrap_resume = create_carnival_alt_resume(packer, cp, can, {**source, "COUNTER": 255, "CRUISE_BUTTONS": 0})
|
||||
self.assertEqual(wrap_resume[1][2], 0)
|
||||
self.assertTrue(safety.safety_tx_hook(libsafety_py.make_CANPacket(wrap_resume[0], wrap_resume[2], wrap_resume[1])))
|
||||
|
||||
def test_ccnc_owned_warnings_preserve_critical_and_obstruction_fields(self):
|
||||
from opendbc.can import CANParser
|
||||
from opendbc.car import structs
|
||||
from opendbc.car.hyundai.hyundaicanfd import create_ccnc
|
||||
|
||||
packer = CANPacker('hyundai_canfd_generated')
|
||||
cp = params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, alt_buttons=True)
|
||||
critical = ('FAULT_FSS', 'FAULT_FCA', 'FAULT_SCC', 'FAULT_ESS')
|
||||
owned = ('FAULT_LSS', 'FAULT_HDA', 'FAULT_DAS', 'FAULT_LFA', 'FAULT_DAW')
|
||||
for active in (False, True):
|
||||
for fault_value in (1, 2, 3):
|
||||
for alert in (5, 7, 8, 9, 10, 14, 21):
|
||||
with self.subTest(active=active, fault_value=fault_value, alert=alert):
|
||||
first = {name: 0 for name in packer.dbc.name_to_msg['CCNC_0x161'].sigs}
|
||||
second = {name: 0 for name in packer.dbc.name_to_msg['CCNC_0x162'].sigs}
|
||||
first.update(COUNTER=17, ALERTS_2=alert, SOUNDS_2=3, DAW_ICON=1, ALERTS_3=17, ALERTS_5=5)
|
||||
second.update({name: fault_value for name in (*critical, *owned)})
|
||||
second['COUNTER'] = 23
|
||||
hud = structs.CarControl.HUDControl()
|
||||
state = structs.CarState(vEgo=20., steeringAngleDeg=0.)
|
||||
frames = create_ccnc(packer, CanBus(cp), False, active, hud, False, False,
|
||||
first, second, None, True, state, True, active)
|
||||
parser = CANParser('hyundai_canfd_generated', [('CCNC_0x161', 0), ('CCNC_0x162', 0)], CanBus(cp).ECAN)
|
||||
parser.update((1_000_000_000, frames))
|
||||
a, b = parser.vl['CCNC_0x161'], parser.vl['CCNC_0x162']
|
||||
self.assertEqual(a['ALERTS_2'], 0 if active and alert == 5 else alert)
|
||||
self.assertEqual(a['SOUNDS_2'], 0 if active and alert == 5 else 3)
|
||||
self.assertEqual(a['DAW_ICON'], 0 if active else 1)
|
||||
self.assertEqual((a['COUNTER'], b['COUNTER']), (17, 23))
|
||||
for name in critical:
|
||||
self.assertEqual(b[name], fault_value)
|
||||
for name in owned:
|
||||
self.assertEqual(b[name], 0 if active and fault_value == 1 else fault_value)
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from opendbc.car import CanData, gen_empty_fingerprint, structs
|
||||
from opendbc.car.hyundai.daw import DawSource
|
||||
from opendbc.car.hyundai.hyundaicanfd import hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.interface import CarInterface
|
||||
from opendbc.car.hyundai.tests.test_ioniq6_stock_parser import params as ioniq6_params
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
|
||||
BASE = 1_000_000_000
|
||||
|
||||
|
||||
def params(canfd):
|
||||
return ioniq6_params(True)[0] if canfd else CarInterface.get_params(
|
||||
CAR.HYUNDAI_SONATA_HYBRID, gen_empty_fingerprint(), [], False, False, False)
|
||||
|
||||
|
||||
def source(canfd, status):
|
||||
data = bytearray.fromhex('1234567898f8') if not canfd else bytearray(range(16))
|
||||
if canfd:
|
||||
data[7] = (data[7] & ~0x38) | (status << 3)
|
||||
data[:2] = hkg_can_fd_checksum(0x11A, None, data).to_bytes(2, 'little')
|
||||
else:
|
||||
data[5] = (data[5] & ~7) | status
|
||||
return bytes(data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('status', range(8))
|
||||
def test_exact_current_advisory_only_transform(status):
|
||||
canfd = False
|
||||
owner = DawSource(params(canfd))
|
||||
data = source(canfd, status)
|
||||
assert not owner.command(BASE)
|
||||
owner.update([(BASE, [CanData(owner.address, data, owner.source_bus)])])
|
||||
command, = owner.command(BASE)
|
||||
expected = bytearray(data)
|
||||
if canfd:
|
||||
if status == 1:
|
||||
expected[7] &= ~0x38
|
||||
expected[:2] = hkg_can_fd_checksum(owner.address, None, expected).to_bytes(2, 'little')
|
||||
elif 1 <= status <= 3:
|
||||
expected[5] &= ~7
|
||||
assert command == CanData(owner.address, bytes(expected), owner.target_bus)
|
||||
assert owner.data == data
|
||||
assert owner.command(BASE + 100_000_000)
|
||||
assert not owner.command(BASE + 100_000_001)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('canfd,fault', [(canfd, fault) for canfd in (False, True)
|
||||
for fault in ('future', 'old', 'reversed', 'length', 'returned', 'wrong_bus', 'wrong_address', 'crc')
|
||||
if canfd or fault != 'crc'])
|
||||
def test_bad_or_missing_sources_cannot_create_replacements(canfd, fault):
|
||||
owner = DawSource(params(canfd))
|
||||
data = source(canfd, 1)
|
||||
stamp, bus, addr = BASE, owner.source_bus, owner.address
|
||||
if fault == 'length':
|
||||
data = data[:-1]
|
||||
elif fault == 'returned':
|
||||
bus += 128
|
||||
elif fault == 'wrong_bus':
|
||||
bus = 0 if canfd else 1
|
||||
elif fault == 'wrong_address':
|
||||
addr += 1
|
||||
elif fault == 'crc':
|
||||
data = bytes([data[0] ^ 1]) + data[1:]
|
||||
elif fault == 'reversed':
|
||||
owner.update([(BASE + 1, [CanData(addr, data, bus)])])
|
||||
owner.update([(stamp, [CanData(addr, data, bus)])])
|
||||
now = BASE - 1 if fault == 'future' else BASE + 100_000_001 if fault == 'old' else BASE
|
||||
assert not owner.command(now)
|
||||
|
||||
|
||||
def test_actual_parser_and_controller_keep_optional_message_out_of_driving_health():
|
||||
cp = params(False)
|
||||
before = cp.to_dict()
|
||||
first, second = CarInterface(cp.as_reader().as_builder()), CarInterface(cp.as_reader().as_builder())
|
||||
data = bytes.fromhex('000000000883')
|
||||
plain = first.update([(BASE, [])])
|
||||
observed = second.update([(BASE, [CanData(0x53E, data, 2)])])
|
||||
assert observed.to_dict() == plain.to_dict()
|
||||
control = structs.CarControl()
|
||||
baseline = first.apply(control.as_reader(), BASE)[1]
|
||||
actual = second.apply(control.as_reader(), BASE)[1]
|
||||
assert [frame for frame in actual if frame[0] != 0x53E] == baseline
|
||||
assert [frame for frame in actual if frame[0] == 0x53E] == [CanData(0x53E, bytes.fromhex('000000000880'), 0)]
|
||||
assert cp.to_dict() == before
|
||||
|
||||
|
||||
def test_unrelated_canfd_profiles_do_not_emit_new_frames():
|
||||
cp = CarInterface.get_params(CAR.KIA_EV6, gen_empty_fingerprint(), [], False, False, False)
|
||||
owner = DawSource(cp)
|
||||
assert not owner.enabled
|
||||
owner.update([(BASE, [CanData(0x11A, source(True, 1), 1)])])
|
||||
assert not owner.command(BASE)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('word', (0, 1, 9, 0x15, 0x5491, 0x5495))
|
||||
def test_unsupported_ioniq6_native_profiles_cannot_emit_advisories(word):
|
||||
cp = params(True)
|
||||
cp.safetyConfigs[-1].safetyParam = word
|
||||
owner = DawSource(cp)
|
||||
assert not owner.enabled
|
||||
owner.update([(BASE, [CanData(0x11A, source(True, 1), owner.source_bus)])])
|
||||
assert not owner.command(BASE)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('word', (0x11, 0x91, 0x811, 0x891, 0x8015, 0x8095, 0x8815, 0x8895))
|
||||
@pytest.mark.parametrize('status', range(8))
|
||||
def test_live_same_bus_canfd_source_is_never_retransmitted(word, status):
|
||||
cp = params(True)
|
||||
cp.safetyConfigs[-1].safetyParam = word
|
||||
owner = DawSource(cp)
|
||||
assert owner.enabled and owner.source_bus == owner.target_bus
|
||||
for tick in range(20):
|
||||
stamp = BASE + tick * 10_000_000
|
||||
data = bytearray(source(True, status))
|
||||
data[2] = tick
|
||||
data[:2] = hkg_can_fd_checksum(owner.address, None, data).to_bytes(2, 'little')
|
||||
owner.update([(stamp, [CanData(owner.address, bytes(data), owner.source_bus)])])
|
||||
assert owner.data == bytes(data)
|
||||
assert owner.command(stamp) == []
|
||||
assert owner.command(stamp + 5_000_000) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize('alternate', (False, True))
|
||||
def test_actual_ioniq6_controller_keeps_live_source_off_sendcan(alternate):
|
||||
cp, _ = ioniq6_params(alternate)
|
||||
before = cp.to_dict()
|
||||
first, second = CarInterface(cp.as_reader().as_builder()), CarInterface(cp.as_reader().as_builder())
|
||||
data = source(True, 1)
|
||||
plain = first.update([(BASE, [])])
|
||||
observed = second.update([(BASE, [CanData(0x11A, data, 1)])])
|
||||
assert observed.to_dict() == plain.to_dict()
|
||||
control = structs.CarControl()
|
||||
baseline = first.apply(control.as_reader(), BASE)[1]
|
||||
actual = second.apply(control.as_reader(), BASE)[1]
|
||||
assert actual == baseline
|
||||
assert all(frame[0] != 0x11A for frame in actual)
|
||||
assert cp.to_dict() == before
|
||||
@@ -271,16 +271,16 @@ class TestEV9FreshMetadataQualification(unittest.TestCase):
|
||||
cp.safetyConfigs[0].safetyParam ^= 1
|
||||
self.assertTrue(qualified(cp))
|
||||
|
||||
def test_typed_builder_and_reader_avoid_copy_with_original_semantics(self):
|
||||
def test_valid_builder_avoids_copy_and_reader_keeps_copy_contract(self):
|
||||
from unittest.mock import patch
|
||||
from opendbc.car.hyundai import ev9_longitudinal
|
||||
cp = self.base()
|
||||
with patch.object(ev9_longitudinal, 'stock_copy', side_effect=AssertionError('unexpected whole CP copy')):
|
||||
self.assertTrue(qualified(cp))
|
||||
with structs.CarParams.from_bytes(cp.to_bytes()) as reader:
|
||||
expected = self.original(reader)
|
||||
with patch.object(ev9_longitudinal, 'stock_copy', side_effect=AssertionError('unexpected whole CP copy')):
|
||||
self.assertEqual(qualified(reader), expected)
|
||||
with patch.object(ev9_longitudinal, 'stock_copy', wraps=ev9_longitudinal.stock_copy) as copied:
|
||||
self.assertTrue(qualified(reader))
|
||||
self.assertEqual(copied.call_count, 1)
|
||||
|
||||
def test_custom_owner_keeps_original_access_and_copy_behavior(self):
|
||||
cp = self.base()
|
||||
|
||||
@@ -149,7 +149,7 @@ class TestHyundaiFingerprint(unittest.TestCase):
|
||||
self.assertEqual(output_parser.vl["CCNC_0x162"]["COUNTER"], 9)
|
||||
self.assertEqual(output_parser.vl["CCNC_0x161"]["BACKGROUND"], 3)
|
||||
self.assertEqual(output_parser.vl["CCNC_0x162"]["SPEEDLIMIT"], 60)
|
||||
self.assertEqual(output_parser.vl["CCNC_0x162"]["FAULT_DAS"], 0)
|
||||
self.assertEqual(output_parser.vl["CCNC_0x162"]["FAULT_DAS"], 1)
|
||||
self.assertEqual(output_parser.vl["CCNC_0x162"]["LEAD_DISTANCE"], 47 if alpha_long else 25)
|
||||
for signal, expected in (("ALERTS_3", 7), ("ALERTS_5", 4), ("SOUNDS_3", 5)):
|
||||
self.assertEqual(output_parser.vl["CCNC_0x161"][signal], expected)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import json
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from opendbc.can import CANPacker
|
||||
from opendbc.car import Bus, CanData, gen_empty_fingerprint, structs
|
||||
from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.hyundai.carcontroller import CarController
|
||||
from opendbc.car.hyundai.hyundaicanfd import create_ioniq6_blindspot_status, hkg_can_fd_checksum
|
||||
from opendbc.car.hyundai.ioniq6_bsm import BlindspotStatus, Ioniq6BlindspotSources
|
||||
@@ -120,7 +118,6 @@ class TestIoniq6BlindspotStatus(unittest.TestCase):
|
||||
t = 10_000_000_000 # CAN BOOTTIME; controller below sees MONOTONIC.
|
||||
ci.update([(t, [packer.make_can_msg("BLINDSPOTS_FRONT_CORNER_2", 1, {"SIDE_DETECT_STATE": 0x10}),
|
||||
packer.make_can_msg("BLINKERS", 1, {})])])
|
||||
ci.CS.out.vEgoRaw = 10.0
|
||||
control = structs.CarControl()
|
||||
_, frames = controller.update(control.as_reader(), ci.CS, t - 9_000_000_000)
|
||||
rear_front = [(addr, payload, bus) for addr, payload, bus in frames if addr in (0x1BA, 0x1E5)]
|
||||
@@ -129,7 +126,6 @@ class TestIoniq6BlindspotStatus(unittest.TestCase):
|
||||
controller.update(control.as_reader(), ci.CS, t - 9_000_000_000 + i * 10_000_000)
|
||||
ci.update([(t + 50_000_000, [packer.make_can_msg("BLINDSPOTS_FRONT_CORNER_2", 1, {"SIDE_DETECT_STATE": 0x10}),
|
||||
packer.make_can_msg("BLINKERS", 1, {})])])
|
||||
ci.CS.out.vEgoRaw = 10.0
|
||||
_, next_frames = controller.update(control.as_reader(), ci.CS, t - 9_000_000_000 + 50_000_000)
|
||||
self.assertEqual([(addr, payload, bus) for addr, payload, bus in next_frames if addr in (0x1BA, 0x1E5)],
|
||||
create_ioniq6_blindspot_status(1, BlindspotStatus(1, 0)))
|
||||
@@ -140,45 +136,3 @@ class TestIoniq6BlindspotStatus(unittest.TestCase):
|
||||
ci.update([(t + 160_000_000, [packer.make_can_msg("WHEEL_SPEEDS", 1, {})])])
|
||||
_, stale = controller.update(control.as_reader(), ci.CS, t - 9_000_000_000 + 160_000_000)
|
||||
self.assertFalse(any(addr in (0x1BA, 0x1E5) for addr, _, _ in stale))
|
||||
|
||||
def test_controller_low_speed_indication_boundary_and_source_preservation(self):
|
||||
cp = long_params()
|
||||
ci = CarInterface(cp)
|
||||
controller = CarController(DBC[cp.carFingerprint], cp)
|
||||
packer = CANPacker(DBC[cp.carFingerprint][Bus.pt])
|
||||
control = structs.CarControl()
|
||||
controller.ioniq6_bsm_counter = 250 # Exercise rollover while crossing the speed threshold.
|
||||
threshold = 5 * CV.MPH_TO_MS
|
||||
# Include repeated crossings; each fresh pair keeps the rolling counter.
|
||||
speeds = (0.0, threshold - 1e-6, threshold, threshold + 1e-6, 10.0, 0.0, threshold)
|
||||
for left, right in ((False, False), (True, False), (False, True), (True, True)):
|
||||
for left_lamp, right_lamp in ((False, False), (True, False), (False, True), (True, True)):
|
||||
for speed in speeds:
|
||||
with self.subTest(left=left, right=right, lamps=(left_lamp, right_lamp), speed=speed):
|
||||
t = 10_000_000_000 + controller.frame * 10_000_000
|
||||
ci.update([(t, [packer.make_can_msg("BLINDSPOTS_FRONT_CORNER_2", 1,
|
||||
{"SIDE_DETECT_STATE": (0x10 if left else 0) | (0x08 if right else 0)}),
|
||||
packer.make_can_msg("BLINKERS", 1, {})])])
|
||||
# Exact float boundary without Cap'n Proto Float32 quantization.
|
||||
out = ci.CS.out
|
||||
ci.CS.out = SimpleNamespace(**{name: getattr(out, name) for name in out.schema.fields})
|
||||
ci.CS.out.vEgoRaw = speed
|
||||
sources = Ioniq6BlindspotSources()
|
||||
sources.observe(corner_source_ns=t, corner_state=(0x10 if left else 0) | (0x08 if right else 0),
|
||||
lamp_source_ns=t, left_lamp=left_lamp, right_lamp=right_lamp)
|
||||
ci.CS.ioniq6_bsm_sources = sources
|
||||
source_status = sources.status(t)
|
||||
counter = controller.ioniq6_bsm_counter
|
||||
_, frames = controller.update(control.as_reader(), ci.CS, t)
|
||||
status_frames = [(addr, payload, bus) for addr, payload, bus in frames if addr in (0x1BA, 0x1E5)]
|
||||
expected = BlindspotStatus(0, 0) if speed < threshold else source_status
|
||||
self.assertEqual(status_frames, create_ioniq6_blindspot_status(counter, expected))
|
||||
self.assertEqual(sources.status(t), source_status)
|
||||
for addr, payload, _ in status_frames:
|
||||
self.assertEqual(payload[2], counter)
|
||||
self.assertEqual(int.from_bytes(payload[:2], "little"), hkg_can_fd_checksum(addr, None, bytearray(payload)))
|
||||
# The speed filter does not change the existing paired-send cadence.
|
||||
for i in range(1, 5):
|
||||
_, skipped = controller.update(control.as_reader(), ci.CS, t + i * 10_000_000)
|
||||
self.assertFalse(any(addr in (0x1BA, 0x1E5) for addr, _, _ in skipped))
|
||||
ci.CS.out = out
|
||||
|
||||
@@ -870,7 +870,7 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
|
||||
card_owner.is_metric = True
|
||||
card_owner.experimental_mode = False
|
||||
card_owner.initial_cruise_speed = 40
|
||||
card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, wheel_context=None: None,
|
||||
card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, slc_approved=None: None,
|
||||
initialize_v_cruise=lambda *_, **kwargs: None, slc_consumed_button=None, toyota=None,
|
||||
slc_cruise_change=None, v_cruise_kph=60.0, v_cruise_cluster_kph=60.0)
|
||||
card_owner.RI = SimpleNamespace(update=lambda _: None)
|
||||
|
||||
@@ -8,7 +8,7 @@ from opendbc.car.hyundai.canfd_stock_aol import qualified_long
|
||||
def eligible(cp):
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
# Genesis keeps its existing longitudinal controller.
|
||||
return cp.carFingerprint not in (CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_CARNIVAL_2025) and qualified_long(cp)
|
||||
return cp.carFingerprint not in (CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN) and qualified_long(cp)
|
||||
|
||||
|
||||
def options(available, control_state, lead):
|
||||
|
||||
@@ -96,8 +96,6 @@ class IsoTpParallelQuery:
|
||||
msg.send(self.request[0], setup_only=len(self.functional_addrs) > 0)
|
||||
|
||||
results = {}
|
||||
interrupted_reads = set()
|
||||
retried_reads = set()
|
||||
start_time = time.monotonic()
|
||||
addrs_responded = set() # track addresses that have ever sent a valid iso-tp frame for timeout logging
|
||||
response_timeouts = {tx_addr: start_time + timeout for tx_addr in self.msg_addrs}
|
||||
@@ -128,15 +126,9 @@ class IsoTpParallelQuery:
|
||||
|
||||
counter = request_counter[tx_addr]
|
||||
expected_response = self.response[counter]
|
||||
request = self.request[counter]
|
||||
if (len(request) == 3 and request[0] == uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER and
|
||||
not dat.startswith((bytes([request[0] + 0x40]) + request[1:], b'\x7f' + request[:1]))):
|
||||
interrupted_reads.add(tx_addr)
|
||||
continue
|
||||
response_valid = dat.startswith(expected_response)
|
||||
|
||||
if response_valid:
|
||||
interrupted_reads.discard(tx_addr)
|
||||
if counter + 1 < len(self.request):
|
||||
response_timeouts[tx_addr] = time.monotonic() + timeout
|
||||
msg.send(self.request[counter + 1])
|
||||
@@ -157,11 +149,6 @@ class IsoTpParallelQuery:
|
||||
cur_time = time.monotonic()
|
||||
for tx_addr in response_timeouts:
|
||||
if cur_time - response_timeouts[tx_addr] > 0:
|
||||
if not request_done[tx_addr] and tx_addr in interrupted_reads and tx_addr not in retried_reads:
|
||||
msgs[tx_addr].send(self.request[request_counter[tx_addr]])
|
||||
response_timeouts[tx_addr] = cur_time + timeout
|
||||
retried_reads.add(tx_addr)
|
||||
continue
|
||||
if not request_done[tx_addr]:
|
||||
if request_counter[tx_addr] > 0:
|
||||
carlog.error(f"iso-tp query timeout after receiving partial response: {tx_addr}")
|
||||
|
||||
@@ -132,8 +132,7 @@ class CarController(CarControllerBase):
|
||||
self.apply_angle_last = CS.out.steeringAngleDeg
|
||||
self.ascent_angle_initialized = True
|
||||
angle_active = False
|
||||
if (angle_active and not self.angle_was_active and
|
||||
self.CP.carFingerprint in (CAR.SUBARU_CROSSTREK_2025, CAR.SUBARU_FORESTER_2022)):
|
||||
if angle_active and not self.angle_was_active and self.CP.carFingerprint != CAR.SUBARU_ASCENT_2023:
|
||||
self.apply_angle_last = CS.out.steeringAngleDeg
|
||||
if self.CP.carFingerprint in (CAR.SUBARU_LEGACY_2025, CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
|
||||
apply_angle = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last,
|
||||
|
||||
@@ -6,8 +6,7 @@ from opendbc.car.interfaces import CarControllerBase
|
||||
from opendbc.car.tesla.teslacan import TeslaCAN
|
||||
from opendbc.car.tesla.preap.stock import PreAPStockCarController
|
||||
from opendbc.car.tesla.hw1 import HW1CarController
|
||||
from opendbc.car.tesla.values import CAR, CarControllerParams, TeslaSafetyFlags
|
||||
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
|
||||
from opendbc.car.tesla.values import CAR, CarControllerParams
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
|
||||
@@ -28,9 +27,6 @@ class CarController(CarControllerBase):
|
||||
self.hw1 = HW1CarController(dbc_names, CP)
|
||||
return
|
||||
self.apply_angle_last = 0
|
||||
self.coop_steer = CooperativeSteeringController()
|
||||
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and bool(CP.safetyConfigs[0].safetyParam & TeslaSafetyFlags.COOP_STEERING)
|
||||
self.steering_limit_info = {}
|
||||
self.packer = CANPacker(dbc_names[Bus.party])
|
||||
self.tesla_can = TeslaCAN(CP, self.packer)
|
||||
|
||||
@@ -50,27 +46,14 @@ class CarController(CarControllerBase):
|
||||
# Tesla EPS enforces disabling steering on heavy lateral override force.
|
||||
# When enabling in a tight curve, we wait until user reduces steering force to start steering.
|
||||
# Canceling is done on rising edge and is handled generically with CC.cruiseControl.cancel
|
||||
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
|
||||
if not lat_active:
|
||||
self.steering_limit_info = {}
|
||||
lat_active = CC.latActive and CS.hands_on_level < 3
|
||||
|
||||
if self.frame % 2 == 0:
|
||||
# Angular rate limit based on speed
|
||||
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
|
||||
lat_active, CarControllerParams, self.VM)
|
||||
|
||||
command_angle, lat_active = self.coop_steer.update(self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM)
|
||||
self.command_angle = command_angle
|
||||
if self.coop_enabled and lat_active:
|
||||
self.steering_limit_info = {
|
||||
"valid": True, "monoTime": now_nanos,
|
||||
"modelLimitErrorDeg": abs(actuators.steeringAngleDeg - self.apply_angle_last),
|
||||
"resumeLimitErrorDeg": self.coop_steer.resume_limit_error_deg,
|
||||
"cooperativeLimitErrorDeg": self.coop_steer.cooperative_limit_error_deg,
|
||||
"cooperativeOffsetDeg": self.coop_steer.cooperative_offset_deg,
|
||||
"combinedLimitErrorDeg": abs(actuators.steeringAngleDeg + self.coop_steer.cooperative_offset_deg - command_angle),
|
||||
}
|
||||
can_sends.append(self.tesla_can.create_steering_control(command_angle, lat_active))
|
||||
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_last, lat_active))
|
||||
|
||||
if self.frame % 10 == 0:
|
||||
can_sends.append(self.tesla_can.create_steering_allowed())
|
||||
@@ -91,7 +74,7 @@ class CarController(CarControllerBase):
|
||||
|
||||
# TODO: HUD control
|
||||
new_actuators = actuators.as_builder()
|
||||
new_actuators.steeringAngleDeg = getattr(self, "command_angle", self.apply_angle_last)
|
||||
new_actuators.steeringAngleDeg = self.apply_angle_last
|
||||
|
||||
self.frame += 1
|
||||
return new_actuators, can_sends
|
||||
|
||||
@@ -5,7 +5,7 @@ from opendbc.car.common.conversions import Conversions as CV
|
||||
from opendbc.car.interfaces import CarStateBase
|
||||
from opendbc.car.tesla.preap.stock import PreAPStockCarState, get_stock_parsers
|
||||
from opendbc.car.tesla.hw1 import HW1CarState, get_hw1_can_parsers
|
||||
from opendbc.car.tesla.values import CAR, DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, STEER_DISENGAGE_THRESHOLD, TeslaFlags, TeslaSafetyFlags
|
||||
from opendbc.car.tesla.values import CAR, DBC, CANBUS, GEAR_MAP, STEER_THRESHOLD, TeslaFlags
|
||||
|
||||
class TeslaScreenCANParser(CANParser):
|
||||
def __init__(self):
|
||||
@@ -31,7 +31,6 @@ class CarState(CarStateBase):
|
||||
self.hands_on_level = 0
|
||||
self.das_control = {}
|
||||
return
|
||||
self.cooperative_steering = CP.carFingerprint == CAR.TESLA_MODEL_3 and bool(CP.safetyConfigs[0].safetyParam & TeslaSafetyFlags.COOP_STEERING)
|
||||
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
|
||||
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"]
|
||||
|
||||
@@ -103,8 +102,7 @@ class CarState(CarStateBase):
|
||||
# FSD disengages using union of handsOnLevel (slow overrides) and high angle rate faults (fast overrides, high speed)
|
||||
eac_error_code = self.can_define.dv["EPAS3S_sysStatus"]["EPAS3S_eacErrorCode"].get(int(epas_status["EPAS3S_eacErrorCode"]), None)
|
||||
ret.steeringDisengage = self.hands_on_level >= 3 or (eac_status == "EAC_INHIBITED" and
|
||||
eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY") or (
|
||||
self.cooperative_steering and abs(ret.steeringTorque) > STEER_DISENGAGE_THRESHOLD)
|
||||
eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY")
|
||||
|
||||
# Cruise state
|
||||
cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp_party.vl["DI_state"]["DI_cruiseState"]), None)
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from opendbc.car import DT_CTRL, rate_limit
|
||||
from opendbc.car.lateral import apply_steer_angle_limits_vm
|
||||
from opendbc.car.tesla.values import CarControllerParams
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
|
||||
DT_LAT_CTRL = DT_CTRL * CarControllerParams.STEER_STEP
|
||||
STEER_RESUME_RATE_LIMIT_RAMP_RATE = 300.0 # deg/s^2
|
||||
|
||||
STEER_OVERRIDE_MIN_TORQUE = 0.5 # Nm
|
||||
STEER_OVERRIDE_MAX_TORQUE = 2.5 # Nm
|
||||
STEER_OVERRIDE_TORQUE_RANGE = STEER_OVERRIDE_MAX_TORQUE - STEER_OVERRIDE_MIN_TORQUE
|
||||
STEER_OVERRIDE_MAX_LAT_ACCEL = 2.0 # m/s^2
|
||||
STEER_OVERRIDE_DELTA_GAIN_LIMIT = 125.0 # deg/s/Nm
|
||||
|
||||
|
||||
def apply_bounds(signal: float, limit: float) -> float:
|
||||
return float(np.clip(signal, -limit, limit))
|
||||
|
||||
|
||||
def apply_deadzone(signal: float, deadzone: float) -> float:
|
||||
return signal - apply_bounds(signal, deadzone)
|
||||
|
||||
|
||||
def get_steer_from_lat_accel(lat_accel: float, v_ego: float, VM: VehicleModel) -> float:
|
||||
curvature = lat_accel / max(1.0, v_ego) ** 2
|
||||
return math.degrees(VM.get_steer_from_curvature(curvature, v_ego, 0.0))
|
||||
|
||||
|
||||
def get_override_torque_to_angle(v_ego: float, VM: VehicleModel) -> float:
|
||||
max_angle = CarControllerParams.ANGLE_LIMITS.STEER_ANGLE_MAX
|
||||
steer_from_lat_accel = apply_bounds(get_steer_from_lat_accel(STEER_OVERRIDE_MAX_LAT_ACCEL, v_ego, VM), max_angle)
|
||||
return steer_from_lat_accel / STEER_OVERRIDE_TORQUE_RANGE
|
||||
|
||||
|
||||
def calc_override_angle_delta_limit(torque: float) -> float:
|
||||
max_gain = CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE / DT_LAT_CTRL / STEER_OVERRIDE_TORQUE_RANGE
|
||||
return torque * min(STEER_OVERRIDE_DELTA_GAIN_LIMIT, max_gain) * DT_LAT_CTRL
|
||||
|
||||
|
||||
class SteerRateLimiter:
|
||||
def __init__(self):
|
||||
self.last = 0.0
|
||||
|
||||
def reset(self, angle: float) -> None:
|
||||
self.last = angle
|
||||
|
||||
def update(self, angle: float, angle_delta_limit: float) -> float:
|
||||
limited = rate_limit(angle, self.last, -angle_delta_limit, angle_delta_limit)
|
||||
self.last = limited
|
||||
return limited
|
||||
|
||||
|
||||
class CooperativeSteeringController:
|
||||
def __init__(self):
|
||||
self.apply_angle_last = 0.0
|
||||
self.coop_apply_angle_last = 0.0
|
||||
self.angle_override = 0.0
|
||||
self.resume_rate_limiter_delta = SteerRateLimiter()
|
||||
self.resume_rate_limiter = SteerRateLimiter()
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
|
||||
def reset_override_state(self, apply_angle: float) -> None:
|
||||
self.apply_angle_last = apply_angle
|
||||
self.angle_override = 0.0
|
||||
self.coop_apply_angle_last = apply_angle
|
||||
|
||||
def reset_resume_state(self, apply_angle: float) -> None:
|
||||
self.resume_rate_limiter_delta.reset(0.0)
|
||||
self.resume_rate_limiter.reset(apply_angle)
|
||||
|
||||
def update_override_angle(self, apply_angle_delta: float, driver_torque: float, v_ego: float, VM: VehicleModel) -> float:
|
||||
driver_torque = apply_deadzone(driver_torque, STEER_OVERRIDE_MIN_TORQUE)
|
||||
torque_to_angle = get_override_torque_to_angle(v_ego, VM)
|
||||
target_angle = driver_torque * torque_to_angle
|
||||
|
||||
holding_torque = self.angle_override / torque_to_angle if abs(v_ego) > 0.1 else 0.0
|
||||
torque_delta = driver_torque - holding_torque
|
||||
angle_delta_limit = calc_override_angle_delta_limit(abs(torque_delta))
|
||||
angle_override_delta = float(np.clip(target_angle - self.angle_override, -angle_delta_limit, angle_delta_limit))
|
||||
|
||||
# Avoid counting model-requested motion and driver-requested motion twice.
|
||||
if angle_override_delta * apply_angle_delta > 0.0:
|
||||
angle_override_delta -= apply_bounds(apply_angle_delta, abs(angle_override_delta))
|
||||
|
||||
self.angle_override += angle_override_delta
|
||||
return self.angle_override
|
||||
|
||||
def unwind_override_angle(self, saturation_error: float) -> None:
|
||||
if self.angle_override * saturation_error > 0.0:
|
||||
self.angle_override -= apply_bounds(saturation_error, abs(self.angle_override))
|
||||
|
||||
def apply_resume_rate_limit(self, lat_active: bool, apply_angle: float) -> float:
|
||||
if not lat_active:
|
||||
self.reset_resume_state(apply_angle)
|
||||
return apply_angle
|
||||
|
||||
angle_rate_delta = self.resume_rate_limiter_delta.update(
|
||||
CarControllerParams.ANGLE_LIMITS.MAX_ANGLE_RATE,
|
||||
STEER_RESUME_RATE_LIMIT_RAMP_RATE * DT_LAT_CTRL ** 2,
|
||||
)
|
||||
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
|
||||
|
||||
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
|
||||
self.resume_limit_error_deg = 0.0
|
||||
self.cooperative_limit_error_deg = 0.0
|
||||
self.cooperative_offset_deg = 0.0
|
||||
if not enabled:
|
||||
self.reset_resume_state(apply_angle)
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, lat_active
|
||||
|
||||
requested_angle = apply_angle
|
||||
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
|
||||
self.resume_limit_error_deg = abs(requested_angle - apply_angle)
|
||||
if not lat_active:
|
||||
self.reset_override_state(apply_angle)
|
||||
return apply_angle, False
|
||||
|
||||
apply_angle_delta = apply_angle - self.apply_angle_last
|
||||
self.apply_angle_last = apply_angle
|
||||
self.cooperative_offset_deg = self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
|
||||
apply_angle += self.cooperative_offset_deg
|
||||
|
||||
limited_angle = apply_steer_angle_limits_vm(
|
||||
apply_angle,
|
||||
self.coop_apply_angle_last,
|
||||
CS.out.vEgoRaw,
|
||||
CS.out.steeringAngleDeg,
|
||||
True,
|
||||
CarControllerParams,
|
||||
VM,
|
||||
)
|
||||
self.coop_apply_angle_last = limited_angle
|
||||
self.cooperative_limit_error_deg = abs(apply_angle - limited_angle)
|
||||
self.unwind_override_angle(apply_angle - limited_angle)
|
||||
return limited_angle, True
|
||||
@@ -15,7 +15,6 @@ FW_VERSIONS = {
|
||||
b'TeM3_ES014p11_0.0.0 (25),ES014.19.0',
|
||||
b'TeM3_E014p10_0.0.0 (24),E014.20.2',
|
||||
b'TeM3_E014p10_0.0.0 (24),EL014.20.2',
|
||||
b'TeM3_E014p10_0.0.0 (35),E014.21.2',
|
||||
b'TeMYG4_DCS_Update_0.0.0 (13),E4014.28.1',
|
||||
b'TeMYG4_DCS_Update_0.0.0 (9),E4014.26.0',
|
||||
b'TeMYG4_Legacy3Y_0.0.0 (2),E4015.02.0',
|
||||
@@ -42,7 +41,6 @@ FW_VERSIONS = {
|
||||
b'TeM3_E014p10_0.0.0 (16),YP002.18.00',
|
||||
b'TeM3_E014p10_0.0.0 (24),Y002.21.2',
|
||||
b'TeM3_E014p10_0.0.0 (24),YP002.21.2',
|
||||
b'TeM3_E014p10_0.0.0 (35),Y002.22.2',
|
||||
b'TeM3_ES014p11_0.0.0 (16),YS002.17',
|
||||
b'TeM3_ES014p11_0.0.0 (25),YS002.19.0',
|
||||
b'TeMYG4_DCS_Update_0.0.0 (13),Y4002.27.1',
|
||||
@@ -59,7 +57,6 @@ FW_VERSIONS = {
|
||||
b'TeMYG4_SingleECU_0.0.0 (28),Y4S002.23.0',
|
||||
b'TeMYG4_SingleECU_0.0.0 (33),Y4S002.26',
|
||||
b'TeMYG4_Main_0.0.0 (87),Y4003.09.3',
|
||||
b'TeMYG4_Main_0.0.0 (92),Y4003.10.2',
|
||||
],
|
||||
},
|
||||
CAR.TESLA_MODEL_X: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Exact modern Tesla screen harness capability and frozen safety selection."""
|
||||
from opendbc.car.tesla.values import CAR, TeslaFlags
|
||||
|
||||
SCREEN_WORDS = frozenset((512, 513, 1536, 1537, 768, 769, 1792, 1793))
|
||||
BASE_WORDS = frozenset((0, 1, 256, 257))
|
||||
SCREEN_WORDS = frozenset((512, 513, 1536, 1537))
|
||||
BASE_WORDS = frozenset((0, 1))
|
||||
|
||||
|
||||
def screen_tap_supported(cp):
|
||||
@@ -14,8 +14,7 @@ def screen_tap_supported(cp):
|
||||
cp.flags & ~int(TeslaFlags.MISSING_DAS_SETTINGS | TeslaFlags.HAS_VEHICLE_BUS | TeslaFlags.AOL_SCREEN_BUTTON)):
|
||||
return False
|
||||
word = int(cp.safetyConfigs[0].safetyParam)
|
||||
return (word in BASE_WORDS | SCREEN_WORDS and (not word & 256 or cp.carFingerprint == CAR.TESLA_MODEL_3) and
|
||||
bool(word & 1) == bool(cp.openpilotLongitudinalControl) and
|
||||
return (word in BASE_WORDS | SCREEN_WORDS and bool(word & 1) == bool(cp.openpilotLongitudinalControl) and
|
||||
bool(word & 512) == bool(cp.flags & TeslaFlags.AOL_SCREEN_BUTTON))
|
||||
except (AttributeError, ValueError, TypeError, IndexError):
|
||||
return False
|
||||
@@ -32,7 +31,7 @@ def screen_qualified(cp, *, marked_only=False):
|
||||
def apply_screen_button(cp, requested, brake_disengage):
|
||||
if not screen_tap_supported(cp):
|
||||
return
|
||||
base = int(cp.safetyConfigs[0].safetyParam) & 257
|
||||
base = int(cp.safetyConfigs[0].safetyParam) & 1
|
||||
cp.flags &= ~int(TeslaFlags.AOL_SCREEN_BUTTON)
|
||||
cp.safetyConfigs[0].safetyParam = base
|
||||
if requested:
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import gen_empty_fingerprint
|
||||
from opendbc.car.tesla.carcontroller import CarController, get_safety_CP
|
||||
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
|
||||
from opendbc.car.tesla.interface import CarInterface
|
||||
from opendbc.car.tesla.values import CAR, DBC, CarControllerParams, TeslaSafetyFlags
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
|
||||
|
||||
def make_car_state(torque=0.0, speed=15.0, angle=0.0):
|
||||
return SimpleNamespace(out=SimpleNamespace(
|
||||
steeringTorque=torque,
|
||||
steeringAngleDeg=angle,
|
||||
vEgo=speed,
|
||||
vEgoRaw=speed,
|
||||
))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vehicle_model():
|
||||
return VehicleModel(get_safety_CP())
|
||||
|
||||
|
||||
def test_disabled_preserves_angle_command(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
angle, lat_active = controller.update(12.5, True, False, make_car_state(torque=2.0), vehicle_model)
|
||||
|
||||
assert angle == 12.5
|
||||
assert lat_active
|
||||
|
||||
|
||||
def test_light_driver_torque_adjusts_angle(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
angle = 0.0
|
||||
|
||||
for _ in range(10):
|
||||
angle, lat_active = controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
|
||||
|
||||
assert lat_active
|
||||
assert angle > 0.0
|
||||
assert angle <= CarControllerParams.ANGLE_LIMITS.STEER_ANGLE_MAX
|
||||
|
||||
|
||||
def test_inactive_lateral_resets_override(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(10):
|
||||
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
|
||||
|
||||
angle, lat_active = controller.update(8.0, False, True, make_car_state(torque=1.5, angle=8.0), vehicle_model)
|
||||
assert angle == 8.0
|
||||
assert not lat_active
|
||||
|
||||
angle, lat_active = controller.update(8.0, True, True, make_car_state(angle=8.0), vehicle_model)
|
||||
assert angle == 8.0
|
||||
assert lat_active
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "enabled", "expected"), (
|
||||
(CAR.TESLA_MODEL_3, False, False),
|
||||
(CAR.TESLA_MODEL_3, True, True),
|
||||
(CAR.TESLA_MODEL_Y, True, False),
|
||||
(CAR.TESLA_MODEL_S_PREAP, True, False),
|
||||
))
|
||||
def test_safety_flag_is_model_3_only(candidate, enabled, expected):
|
||||
params = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, True)
|
||||
from openpilot.starpilot.car.tesla.coop_preferences import prepare
|
||||
prepare(params, enabled)
|
||||
|
||||
has_flag = any(config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in params.safetyConfigs)
|
||||
assert has_flag is expected
|
||||
|
||||
if candidate != CAR.TESLA_MODEL_S_PREAP:
|
||||
assert CarController(DBC[candidate], params).coop_enabled is expected
|
||||
|
||||
|
||||
def assert_finite_nonnegative_limit_errors(controller):
|
||||
errors = (controller.resume_limit_error_deg, controller.cooperative_limit_error_deg)
|
||||
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
|
||||
assert math.isfinite(controller.cooperative_offset_deg)
|
||||
|
||||
|
||||
def test_zero_torque_has_zero_cooperative_diagnostics(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
angle, lat_active = controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert angle == 0.0
|
||||
assert lat_active
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_steady_light_torque_reports_offset_without_real_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg > 2.5
|
||||
assert controller.resume_limit_error_deg < 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_torque_reversal_updates_signed_offset_without_negative_errors(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
|
||||
|
||||
for _ in range(200):
|
||||
controller.update(0.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert controller.cooperative_offset_deg < -2.5
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_release_reports_gradual_offset_unwind(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
|
||||
|
||||
offsets = []
|
||||
for _ in range(100):
|
||||
controller.update(0.0, True, True, make_car_state(), vehicle_model)
|
||||
offsets.append(controller.cooperative_offset_deg)
|
||||
|
||||
assert offsets[0] > offsets[-1] >= 0.0
|
||||
assert all(next_offset <= offset for offset, next_offset in zip(offsets, offsets[1:], strict=False))
|
||||
assert offsets[-1] == pytest.approx(0.0, abs=1e-6)
|
||||
assert_finite_nonnegative_limit_errors(controller)
|
||||
|
||||
|
||||
def test_resume_ramp_reports_resume_limiting(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(0.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg > 2.5
|
||||
assert controller.cooperative_limit_error_deg < 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_reports_cooperative_target_clipping(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
|
||||
|
||||
def test_final_limiter_remains_visible_with_light_torque(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.reset_override_state(0.0)
|
||||
|
||||
controller.update(20.0, True, True, make_car_state(torque=-0.9), vehicle_model)
|
||||
|
||||
assert abs(controller.cooperative_offset_deg) > 0.0
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
|
||||
def test_diagnostics_reset_on_disabled_update(vehicle_model):
|
||||
controller = CooperativeSteeringController()
|
||||
controller.reset_resume_state(20.0)
|
||||
controller.update(20.0, True, True, make_car_state(), vehicle_model)
|
||||
assert controller.cooperative_limit_error_deg > 2.5
|
||||
|
||||
controller.update(4.0, True, False, make_car_state(torque=2.0), vehicle_model)
|
||||
|
||||
assert controller.resume_limit_error_deg == 0.0
|
||||
assert controller.cooperative_limit_error_deg == 0.0
|
||||
assert controller.cooperative_offset_deg == 0.0
|
||||
@@ -52,7 +52,7 @@ def test_no_harness_old_encoding_malformed_cp_or_reused_flags():
|
||||
before = cp.to_bytes()
|
||||
apply_screen_button(cp, True, True)
|
||||
assert cp.to_bytes() == before
|
||||
for word in (2, 3, 16, 258, 1024, 514, 1538):
|
||||
for word in (2, 3, 16, 256, 1024, 514, 1538):
|
||||
cp = screen_params()
|
||||
cp.safetyConfigs[0].safetyParam = word
|
||||
assert not screen_tap_supported(cp)
|
||||
|
||||
@@ -132,7 +132,6 @@ class CarControllerParams:
|
||||
class TeslaSafetyFlags(IntFlag):
|
||||
LONG_CONTROL = 1
|
||||
HW1 = 16
|
||||
COOP_STEERING = 256
|
||||
AOL_SCREEN_BUTTON = 512
|
||||
AOL_SCREEN_DISENGAGE_ON_BRAKE = 1024
|
||||
|
||||
@@ -154,5 +153,3 @@ class TeslaFlags(IntFlag):
|
||||
DBC = CAR.create_dbc_map()
|
||||
|
||||
STEER_THRESHOLD = 1
|
||||
|
||||
STEER_DISENGAGE_THRESHOLD = 5.0
|
||||
|
||||
@@ -153,7 +153,7 @@ class CarTestRoute(NamedTuple):
|
||||
routes = [
|
||||
CarTestRoute("efdf9af95e71cd84/2022-05-13--19-03-31", COMMA.COMMA_BODY),
|
||||
|
||||
CarTestRoute("c5e3aa51055c8f47/2023-12-06--20-01-44", CHRYSLER.JEEP_CHEROKEE_5TH_GEN),
|
||||
CarTestRoute("c5e3aa51055c8f47|2023-12-06--20-01-44", CHRYSLER.JEEP_CHEROKEE_5TH_GEN),
|
||||
CarTestRoute("0c94aa1e1296d7c6/2021-05-05--19-48-37", CHRYSLER.JEEP_GRAND_CHEROKEE),
|
||||
CarTestRoute("91dfedae61d7bd75/2021-05-22--20-07-52", CHRYSLER.JEEP_GRAND_CHEROKEE_2019),
|
||||
CarTestRoute("420a8e183f1aed48/2020-03-05--07-15-29", CHRYSLER.CHRYSLER_PACIFICA_2018_HYBRID), # 2017
|
||||
@@ -410,7 +410,7 @@ routes = [
|
||||
CarTestRoute("ffcd23abbbd02219/2024-02-28--14-59-38", VOLKSWAGEN.VOLKSWAGEN_CADDY_MK3),
|
||||
CarTestRoute("cae14e88932eb364/2021-03-26--14-43-28", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # Stock ACC
|
||||
CarTestRoute("3cfdec54aa035f3f/2022-10-13--14-58-58", VOLKSWAGEN.VOLKSWAGEN_GOLF_MK7), # openpilot longitudinal
|
||||
CarTestRoute("578742b26807f756/00000010--41ee3e5bec", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK6),
|
||||
CarTestRoute("578742b26807f756|00000010--41ee3e5bec", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK6),
|
||||
CarTestRoute("58a7d3b707987d65/2021-03-25--17-26-37", VOLKSWAGEN.VOLKSWAGEN_JETTA_MK7),
|
||||
CarTestRoute("4d134e099430fba2/2021-03-26--00-26-06", VOLKSWAGEN.VOLKSWAGEN_PASSAT_MK8),
|
||||
CarTestRoute("3cfdec54aa035f3f/2022-07-19--23-45-10", VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS),
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from opendbc.car import CanData, structs
|
||||
from opendbc.car.fw_versions import match_fw_to_car
|
||||
from opendbc.car.hyundai.fingerprints import FW_VERSIONS
|
||||
from opendbc.car.hyundai.values import CAR
|
||||
from opendbc.car.isotp_parallel_query import IsoTpParallelQuery
|
||||
from opendbc.car.vin import VIN_UNKNOWN
|
||||
|
||||
|
||||
CAMERA = FW_VERSIONS[CAR.KIA_CARNIVAL_2025][(structs.CarParams.Ecu.fwdCamera, 0x7C4, None)][0]
|
||||
|
||||
|
||||
class DiagnosticBus:
|
||||
def __init__(self, replies):
|
||||
self.replies = replies
|
||||
self.now = 0.
|
||||
self.requests = []
|
||||
self.pending = []
|
||||
self.consecutive = []
|
||||
|
||||
def schedule(self, payload, delay=.01, bus=1):
|
||||
if len(payload) <= 7:
|
||||
self.pending.append((self.now + delay, CanData(0x7CC, bytes([len(payload)]) + payload, bus)))
|
||||
else:
|
||||
self.pending.append((self.now + delay, CanData(0x7CC, bytes([0x10, len(payload)]) + payload[:6], bus)))
|
||||
self.consecutive = [CanData(0x7CC, bytes([0x20 | (i % 16)]) + payload[j:j+7], bus)
|
||||
for i, j in enumerate(range(6, len(payload), 7), 1)]
|
||||
|
||||
def send(self, packets):
|
||||
for address, data, bus in packets:
|
||||
assert address == 0x7C4 and bus == 1
|
||||
if data[0] == 0x30:
|
||||
self.pending.extend((self.now + .01 * i, frame) for i, frame in enumerate(self.consecutive, 1))
|
||||
self.consecutive = []
|
||||
else:
|
||||
request = data[1:1+data[0]]
|
||||
self.requests.append(request)
|
||||
for payload, delay, response_bus in self.replies(len(self.requests)):
|
||||
self.schedule(payload, delay, response_bus)
|
||||
|
||||
def recv(self, wait_for_one=False):
|
||||
if wait_for_one:
|
||||
self.now += .01
|
||||
ready = [frame for deadline, frame in self.pending if deadline <= self.now]
|
||||
self.pending = [(deadline, frame) for deadline, frame in self.pending if deadline > self.now]
|
||||
return [ready] if ready else []
|
||||
|
||||
def query(self, request=b'\x22\xf1\x00', response=b'\x62', total_timeout=1.):
|
||||
with patch('opendbc.car.isotp_parallel_query.time.monotonic', side_effect=lambda: self.now):
|
||||
return IsoTpParallelQuery(self.send, self.recv, 1, [0x7C4], [request], [response]).get_data(.05, total_timeout)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('foreign', (b'\x59\x02\x0c\x62\x39\x86\x08', b'\x7f\x19\x78',
|
||||
b'\x62\xf1\x90x'))
|
||||
@pytest.mark.parametrize('same_attempt', (False, True))
|
||||
def test_foreign_diagnostic_reply_cannot_finish_firmware_query(foreign, same_attempt):
|
||||
def replies(attempt):
|
||||
if attempt == 1:
|
||||
result = [(foreign, .01, 1)]
|
||||
if same_attempt:
|
||||
result.append((b'\x62' + CAMERA, .03, 1))
|
||||
return result
|
||||
return [(b'\x62' + CAMERA, .01, 1)]
|
||||
bus = DiagnosticBus(replies)
|
||||
assert bus.query() == {(0x7C4, None): CAMERA}
|
||||
assert bus.requests == [b'\x22\xf1\x00'] * (1 if same_attempt else 2)
|
||||
assert bus.now < .3
|
||||
|
||||
|
||||
def test_complete_transport_recovery_still_requires_both_existing_firmware_identities():
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x59\x02\x0c\x62\x39\x86\x08', .01, 1)] if attempt == 1
|
||||
else [(b'\x62' + CAMERA, .01, 1)])
|
||||
camera = bus.query()[(0x7C4, None)]
|
||||
radar = FW_VERSIONS[CAR.KIA_CARNIVAL_2025][(structs.CarParams.Ecu.fwdRadar, 0x7D0, None)][-1]
|
||||
fw = [structs.CarParams.CarFw(ecu=ecu, address=address, brand='hyundai', fwVersion=version, bus=1)
|
||||
for ecu, address, version in ((structs.CarParams.Ecu.fwdCamera, 0x7C4, camera),
|
||||
(structs.CarParams.Ecu.fwdRadar, 0x7D0, radar))]
|
||||
assert match_fw_to_car(fw, VIN_UNKNOWN, log=False) == (True, {CAR.KIA_CARNIVAL_2025})
|
||||
assert not match_fw_to_car(fw[1:], VIN_UNKNOWN, log=False)[1]
|
||||
fw[0].fwVersion = camera + b'unknown'
|
||||
assert not match_fw_to_car(fw, VIN_UNKNOWN, log=False)[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('payload', (b'\x7f\x22\x31', b'\x7f\x22\x21'))
|
||||
def test_current_request_negative_response_remains_terminal(payload):
|
||||
bus = DiagnosticBus(lambda attempt: [(payload, .01, 1)])
|
||||
assert bus.query() == {}
|
||||
assert len(bus.requests) == 1
|
||||
|
||||
|
||||
def test_continuous_foreign_traffic_gets_only_one_read_retry():
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x59\x02\x0c', .01, 1)])
|
||||
assert bus.query() == {}
|
||||
assert len(bus.requests) == 2
|
||||
assert bus.now < .15
|
||||
|
||||
|
||||
@pytest.mark.parametrize('response_bus', (0, 2, 129, 193))
|
||||
def test_wrong_bus_and_echo_cannot_supply_identity_or_trigger_retry(response_bus):
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x62' + CAMERA, .01, response_bus)])
|
||||
assert bus.query() == {}
|
||||
assert len(bus.requests) == 1
|
||||
|
||||
|
||||
def test_actuation_service_is_never_retried():
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x59\x02\x0c', .01, 1)])
|
||||
assert bus.query(b'\x28\x83\x03', b'\x68') == {}
|
||||
assert bus.requests == [b'\x28\x83\x03']
|
||||
|
||||
|
||||
def test_matching_pending_response_keeps_existing_wait_contract():
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x7f\x22\x78', .01, 1), (b'\x62' + CAMERA, .12, 1)])
|
||||
assert bus.query() == {(0x7C4, None): CAMERA}
|
||||
assert len(bus.requests) == 1
|
||||
|
||||
|
||||
def test_foreign_pending_cannot_extend_total_deadline():
|
||||
bus = DiagnosticBus(lambda attempt: [(b'\x7f\x19\x78', .01, 1)])
|
||||
assert bus.query(total_timeout=.075) == {}
|
||||
assert len(bus.requests) == 2
|
||||
assert bus.now <= .1
|
||||
@@ -106,8 +106,9 @@ class CarInterface(CarInterfaceBase):
|
||||
# Only give steer angle deadzone to for bad angle sensor prius
|
||||
for fw in car_fw:
|
||||
if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00':
|
||||
ret.steerActuatorDelay = 0.14
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.3)
|
||||
retrofit = candidate == CAR.TOYOTA_PRIUS_RETROFIT or smart_dsu
|
||||
ret.steerActuatorDelay = 0.14 if retrofit else 0.25
|
||||
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.3 if retrofit else 0.2)
|
||||
# 2021+ TSS2 steering rack swapped into a TSS-P car, not supported
|
||||
if candidate == CAR.TOYOTA_PRIUS and not smart_dsu and fw.ecu == "eps" and fw.fwVersion == b'8965B47070\x00\x00\x00\x00\x00\x00':
|
||||
ret.dashcamOnly = True
|
||||
|
||||
@@ -13,11 +13,11 @@ PROFILES = {
|
||||
}
|
||||
|
||||
|
||||
def _cluster_radar_bus(candidate, bus, messages):
|
||||
# The 2018–20 Camry and 2017–20 C-HR expose an independent eight-byte 2FF stream alongside
|
||||
def _camry_radar_bus(candidate, bus, messages):
|
||||
# The 2018–20 Camry exposes an independent eight-byte 2FF stream alongside
|
||||
# its six cluster-radar frames on bus1 (cluster_radar.CLUSTERS). It is not
|
||||
# the four-byte filter advertisement or a bus0 smartDSU status source.
|
||||
return (candidate in (CAR.TOYOTA_CAMRY, CAR.TOYOTA_CHR) and bus == 1 and messages.get(0x2FF) == 8 and
|
||||
return (candidate == CAR.TOYOTA_CAMRY and bus == 1 and messages.get(0x2FF) == 8 and
|
||||
all(messages.get(address) == 8 for address in range(0x680, 0x686)))
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@ def hardware_supported(candidate, fingerprint):
|
||||
# Panda discovery may also expose its forwarded camera-bus copy.
|
||||
if (candidate in PROFILES and fingerprint.get(0, {}).get(0x2FF) == 4 and
|
||||
not any(address in messages for messages in fingerprint.values() for address in (0x201, 0x2AA)) and
|
||||
all((bus in (0, 2) and messages[0x2FF] == 4) or _cluster_radar_bus(candidate, bus, messages)
|
||||
all((bus in (0, 2) and messages[0x2FF] == 4) or _camry_radar_bus(candidate, bus, messages)
|
||||
for bus, messages in fingerprint.items() if 0x2FF in messages)):
|
||||
return True
|
||||
return (candidate in PROFILES and fingerprint.get(0, {}).get(0x2AA) == 8 and
|
||||
all(bus in (0, 2) and frames[0x2AA] == 8 for bus, frames in fingerprint.items() if 0x2AA in frames) and
|
||||
not any(0x201 in frames or 0x2FF in frames and not _cluster_radar_bus(candidate, bus, frames)
|
||||
not any(0x201 in frames or 0x2FF in frames and not _camry_radar_bus(candidate, bus, frames)
|
||||
for bus, frames in fingerprint.items()))
|
||||
|
||||
|
||||
|
||||
@@ -594,6 +594,11 @@ BO_ 512 ADRV_0x200: 8 ADRV
|
||||
SG_ SET_ME_E1 : 24|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ SET_ME_3A : 32|8@1+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 517 DRIVE_MODE_EV: 8 XXX
|
||||
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ DRIVE_MODE : 24|8@1+ (1,0) [0|255] "" XXX
|
||||
|
||||
BO_ 593 RADAR_0x251: 16 FRONT_RADAR
|
||||
SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX
|
||||
SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX
|
||||
@@ -1152,6 +1157,7 @@ VAL_ 506 ISLA_AutoUsmSta 0 "None Auto Function (Delete Menu)" 1 "Auto Off" 2 "Au
|
||||
VAL_ 506 ISLA_Cntry 0 "Europe/Russia/Australia" 1 "Domestic" 2 "China" 3 "USA" 4 "Canada" 5 "Australia" 6 "Reserved" 7 "Reserved" 8 "Reserved" 9 "Reserved" 10 "Reserved" 11 "Reserved" 12 "Reserved" 13 "Reserved" 14 "Reserved" 15 "Initial Value (default)";
|
||||
VAL_ 506 ISLA_AddtnlSign 0 "No Recognition (default)" 1 "School Crossing" 16 "Do Not Pass" 17 "Reserved" 18 "Reserved" 19 "Reserved" 20 "Reserved" 21 "Reserved" 22 "Reserved" 23 "Reserved" 24 "Exit" 25 "Roundabout" 26 "Right Curve" 27 "Left Curve" 28 "Winding Road" 29 "Reserved" 30 "Reserved" 31 "Reserved" 2 "Pedestrian Crossing" 3 "Bicycle Crossing" 4 "Reserved" 5 "Reserved" 6 "Reserved" 7 "Reserved" 8 "Stop" 9 "Yield" 10 "Stop Ahead" 11 "Yield Ahead" 12 "Road Construction Ahead" 13 "Lane Reduction" 14 "Reserved" 15 "Reserved";
|
||||
VAL_ 506 ISLA_SchoolZone 0 "No School Zone" 1 "School Zone" 2 "Reserved" 3 "Reserved";
|
||||
VAL_ 517 DRIVE_MODE 2 "Normal" 4 "Eco" 5 "Sport";
|
||||
VAL_ 687 HOD_Dir_Status 0 "HANDS OFF" 1 "HAND TOUCH (SOFT)" 2 "HAND TOUCH (STRONG)" 3 "HAND GRIP (SOFT)" 4 "HAND GRIP (STRONG)" 5 "RESERVED" 6 "RESERVED";
|
||||
VAL_ 698 IFSref_FR_CMR_Sta 0 "None Option (Default)" 1 "Normal" 2 "Blockage Status" 3 "Error Indicator";
|
||||
VAL_ 698 IFSref_VehNumVal 0 "No vehicle" 1 "Number of vehicles" 2 "Number of vehicles" 3 "Number of vehicles" 4 "Number of vehicles" 5 "Number of vehicles" 6 "Number of vehicles" 7 "Number of vehicles" 8 "Number of vehicles" 9 "Number of vehicles" 10 "Number of vehicles" 11 "Over than 10 vehicles" 12 "Reserved" 13 "Reserved" 14 "Default" 15 "Error indicator";
|
||||
|
||||
@@ -217,7 +217,6 @@ BO_ 258 ESC_50: 48 XXX
|
||||
BO_ 261 VMM_01: 8 XXX
|
||||
SG_ CRC : 0|8@1+ (1,0) [0|255] "" XXX
|
||||
SG_ CNT : 8|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ HMS_Status : 36|4@1+ (1,0) [0|15] "" XXX
|
||||
SG_ Brake : 53|7@1+ (1,0) [0|3] "" XXX
|
||||
|
||||
BO_ 278 ESP_10: 8 Gateway_MQB
|
||||
@@ -2028,7 +2027,6 @@ CM_ SG_ 219 AWV_Init_Status "Power-up state only, reads 2 once running. Does not
|
||||
CM_ SG_ 219 Unknown_192 "Constant per platform (0 on all gen1 ID.4/Born, 1 on all gen2 ID.4)";
|
||||
CM_ SG_ 219 Unknown_27 "Low bit tracks AWV_Unavailable, but is also set in 20-33% of normal driving";
|
||||
CM_ SG_ 219 Unknown_80 "Low bit is normally 1 and clears with AWV_Unavailable, but also clears in ~1% of normal driving";
|
||||
CM_ SG_ 261 HMS_Status "Hold manager response to ACC_18.ACC_Anforderung_HMS, pre-2024 only. 2024+ reports it in VMM_02";
|
||||
CM_ SG_ 313 Long_Control_Inhibit "Goes high after user brakes hard, causes temporary TSK_Status fault";
|
||||
CM_ SG_ 313 HMS_Status "Hold manager response to ACC_18.ACC_Anforderung_HMS and auto hold, 2024+ only";
|
||||
CM_ SG_ 333 SET_ME_0x2FE "Only set by stock ACC on 2024+; reads 0 on pre-2024 and is not packed by mebcan";
|
||||
@@ -2131,7 +2129,6 @@ VAL_ 253 EDS_Eingriff 0 "kein_EDS_Eingriff" 1 "EDS_Eingriff";
|
||||
VAL_ 253 ESP_Eingriff 0 "kein_ESP_Eingriff" 1 "ESP_Eingriff_aktiv";
|
||||
VAL_ 253 ESP_ASP 0 "inaktiv" 1 "aktiv";
|
||||
VAL_ 253 ESC_Neutralschaltung 0 "keine_Anforderung" 1 "Neutralschaltung_angefordert";
|
||||
VAL_ 261 HMS_Status 0 "not_holding" 1 "holding" 5 "starting" 6 "releasing" 9 "stopping";
|
||||
VAL_ 267 TSK_Status 0 "init" 1 "disabled" 2 "enabled" 3 "regulating" 4 "accel_pedal_override" 5 "brake_only" 6 "temp_fault" 7 "perm_fault";
|
||||
VAL_ 278 ESP_QBit_Wegimpuls_VL 0 "gueltiger_Wert" 1 "Ersatz_Init_oder_Fehlerwert";
|
||||
VAL_ 278 ESP_QBit_Wegimpuls_VR 0 "gueltiger_Wert" 1 "Ersatz_Init_oder_Fehlerwert";
|
||||
|
||||
@@ -42,16 +42,13 @@
|
||||
#define GET_BIT(msg, b) ((bool)!!(((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U))
|
||||
#define GET_FLAG(value, mask) (((value) & (mask)) == (mask))
|
||||
|
||||
// length of an RX check array; fails to compile if it has more checks than rx_status can track
|
||||
#define RX_CHECKS_LEN(rx) ((sizeof((rx)) / sizeof((rx)[0])) + \
|
||||
(0U * sizeof(char[((sizeof((rx)) / sizeof((rx)[0])) <= MAX_RX_CHECKS) ? 1 : -1])))
|
||||
#define BUILD_SAFETY_CFG(rx, tx) ((safety_config){(rx), RX_CHECKS_LEN(rx), \
|
||||
#define BUILD_SAFETY_CFG(rx, tx) ((safety_config){(rx), (sizeof((rx)) / sizeof((rx)[0])), \
|
||||
(tx), (sizeof((tx)) / sizeof((tx)[0])), \
|
||||
false})
|
||||
#define SET_RX_CHECKS(rx, config) \
|
||||
do { \
|
||||
(config).rx_checks = (rx); \
|
||||
(config).rx_checks_len = RX_CHECKS_LEN(rx); \
|
||||
(config).rx_checks_len = sizeof((rx)) / sizeof((rx)[0]); \
|
||||
(config).disable_forwarding = false; \
|
||||
} while (0);
|
||||
|
||||
@@ -70,8 +67,6 @@ uint64_t GET_BYTES_64_LE(const CANPacket_t *msg, int start, int len);
|
||||
|
||||
extern const int MAX_WRONG_COUNTERS;
|
||||
#define MAX_ADDR_CHECK_MSGS 3U
|
||||
// max number of RX checks in a safety config; their dynamic state is shared by all safety modes
|
||||
#define MAX_RX_CHECKS 17U
|
||||
#define MAX_SAMPLE_VALS 6
|
||||
// used to represent floating point vehicle speed in a sample_t
|
||||
#define VEHICLE_SPEED_FACTOR 1000.0
|
||||
@@ -95,8 +90,8 @@ struct lookup_t {
|
||||
|
||||
typedef struct {
|
||||
int addr;
|
||||
uint8_t bus;
|
||||
uint8_t len;
|
||||
unsigned int bus;
|
||||
int len;
|
||||
bool check_relay; // if true, trigger relay malfunction if existence on destination bus and block forwarding to destination bus
|
||||
bool disable_static_blocking; // if true, static blocking is disabled so safety mode can dynamically handle it (e.g. selective AEB pass-through)
|
||||
bool check_relay_immediately; // if true, skip the usual transition grace for this exact TX address and bus
|
||||
@@ -183,38 +178,36 @@ typedef struct {
|
||||
} LongitudinalLimits;
|
||||
|
||||
typedef struct {
|
||||
unsigned int addr : 29;
|
||||
unsigned int bus : 3;
|
||||
unsigned int len : 7;
|
||||
unsigned int frequency : 7; // expected frequency of the message [Hz]
|
||||
bool ignore_checksum : 1; // checksum check is not performed when set to true
|
||||
bool ignore_counter : 1; // counter check is not performed when set to true
|
||||
unsigned int max_counter : 8; // maximum value of the counter. 0 means that the counter check is skipped
|
||||
bool ignore_quality_flag : 1; // true if quality flag check is skipped
|
||||
int addr;
|
||||
unsigned int bus;
|
||||
int len;
|
||||
uint32_t frequency; // expected frequency of the message [Hz]
|
||||
bool ignore_checksum; // checksum check is not performed when set to true
|
||||
bool ignore_counter; // counter check is not performed when set to true
|
||||
uint8_t max_counter; // maximum value of the counter. 0 means that the counter check is skipped
|
||||
bool ignore_quality_flag; // true if quality flag check is skipped
|
||||
} CanMsgCheck;
|
||||
|
||||
// dynamic state of an RX check, reset on safety mode init
|
||||
typedef struct {
|
||||
uint32_t last_timestamp; // micro-s
|
||||
int wrong_counters; // counter of wrong counters, saturated between 0 and MAX_WRONG_COUNTERS
|
||||
uint8_t index; // if multiple messages are allowed to be checked, this stores the index of the first one seen. only msg[msg_index] will be used
|
||||
uint8_t last_counter; // last counter value
|
||||
// dynamic flags, reset on safety mode init
|
||||
bool msg_seen;
|
||||
int index; // if multiple messages are allowed to be checked, this stores the index of the first one seen. only msg[msg_index] will be used
|
||||
bool valid_checksum; // true if and only if checksum check is passed
|
||||
int wrong_counters; // counter of wrong counters, saturated between 0 and MAX_WRONG_COUNTERS
|
||||
bool valid_quality_flag; // true if the message's quality/health/status signals are valid
|
||||
uint8_t last_counter; // last counter value
|
||||
uint32_t last_timestamp; // micro-s
|
||||
bool lagging; // true if and only if the time between updates is excessive
|
||||
} RxStatus;
|
||||
|
||||
// params and flags about checksum, counter and frequency checks for each monitored address.
|
||||
// constant: the state of the current safety config's checks is in rx_status, indexed alike
|
||||
// params and flags about checksum, counter and frequency checks for each monitored address
|
||||
typedef struct {
|
||||
CanMsgCheck msg[MAX_ADDR_CHECK_MSGS]; // check either messages (e.g. honda steer)
|
||||
RxStatus status;
|
||||
} RxCheck;
|
||||
|
||||
extern RxStatus rx_status[MAX_RX_CHECKS];
|
||||
|
||||
typedef struct {
|
||||
const RxCheck *rx_checks;
|
||||
RxCheck *rx_checks;
|
||||
int rx_checks_len;
|
||||
const CanMsg *tx_msgs;
|
||||
int tx_msgs_len;
|
||||
@@ -249,6 +242,8 @@ int to_signed(int d, int bits);
|
||||
void update_sample(struct sample_t *sample, int sample_new);
|
||||
bool get_longitudinal_allowed(void);
|
||||
int ROUND(float val);
|
||||
void gen_crc_lookup_table_8(uint8_t poly, uint8_t crc_lut[]);
|
||||
void gen_crc_lookup_table_16(uint16_t poly, uint16_t crc_lut[]);
|
||||
bool steer_torque_cmd_checks(int desired_torque, int steer_req, const TorqueSteeringLimits limits);
|
||||
bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits);
|
||||
bool steer_angle_cmd_checks_vm(int desired_angle, bool steer_control_enabled, const AngleSteeringLimits limits,
|
||||
|
||||
@@ -43,7 +43,7 @@ static bool body_tx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
static safety_config body_init(uint16_t param) {
|
||||
static const RxCheck body_rx_checks[] = {
|
||||
static RxCheck body_rx_checks[] = {
|
||||
{.msg = {{0x201, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ static safety_config byd_init(uint16_t param) {
|
||||
{0x3B0, 0, 8, .check_relay = false}, // PCM_BUTTONS (cruise cancel button spoof)
|
||||
};
|
||||
|
||||
static const RxCheck byd_rx_checks[] = {
|
||||
static RxCheck byd_rx_checks[] = {
|
||||
{.msg = {{0x11F, 0, 5, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // STEER_MODULE_2 (steering angle)
|
||||
{.msg = {{0x1F0, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // WHEELSPEED_CLEAN (vehicle speed)
|
||||
{.msg = {{0x242, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // DRIVE_STATE (gas and brake pressed)
|
||||
|
||||
@@ -20,7 +20,7 @@ static bool chrysler_hold_ready(void) {
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
bool healthy = !safety_rx_checks_invalid;
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
const RxStatus *status = &rx_status[i];
|
||||
const RxStatus *status = ¤t_safety_config.rx_checks[i].status;
|
||||
healthy &= status->msg_seen && status->valid_checksum && status->valid_quality_flag &&
|
||||
(status->wrong_counters < MAX_WRONG_COUNTERS) && !status->lagging &&
|
||||
(safety_get_ts_elapsed(now, status->last_timestamp) <= 1000000U);
|
||||
@@ -196,7 +196,7 @@ static safety_config chrysler_init(uint16_t param) {
|
||||
chrysler_hold_stock_timestamp = 0U;
|
||||
for (int i = 0; i < 8; i++) { chrysler_hold_stock[i] = 0U; }
|
||||
|
||||
static const RxCheck chrysler_ram_dt_rx_checks[] = {
|
||||
static RxCheck chrysler_ram_dt_rx_checks[] = {
|
||||
{.msg = {{CHRYSLER_RAM_DT_EPS_2, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{CHRYSLER_RAM_DT_ESP_1, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{CHRYSLER_RAM_DT_ESP_8, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
@@ -204,7 +204,7 @@ static safety_config chrysler_init(uint16_t param) {
|
||||
{.msg = {{CHRYSLER_RAM_DT_DAS_3, 2, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
static const RxCheck chrysler_rx_checks[] = {
|
||||
static RxCheck chrysler_rx_checks[] = {
|
||||
{.msg = {{CHRYSLER_EPS_2, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{CHRYSLER_ESP_1, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{514, 0, 8, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
@@ -232,7 +232,7 @@ static safety_config chrysler_init(uint16_t param) {
|
||||
};
|
||||
|
||||
#ifdef ALLOW_DEBUG
|
||||
static const RxCheck chrysler_ram_hd_rx_checks[] = {
|
||||
static RxCheck chrysler_ram_hd_rx_checks[] = {
|
||||
{.msg = {{CHRYSLER_RAM_HD_EPS_2, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{CHRYSLER_RAM_HD_ESP_1, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{CHRYSLER_RAM_HD_ESP_8, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
|
||||
@@ -9,7 +9,7 @@ static safety_config chrysler_cusw_init(uint16_t param) {
|
||||
{0x2FAU, 0, 3, .check_relay = false},
|
||||
};
|
||||
|
||||
static const RxCheck chrysler_cusw_rx_checks[] = {
|
||||
static RxCheck chrysler_cusw_rx_checks[] = {
|
||||
{.msg = {{0x1E4U, 0, 8, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{0x1E8U, 0, 5, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{0x1ECU, 0, 8, 100U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
|
||||
@@ -1160,7 +1160,7 @@ static safety_config gm_init(uint16_t safety_param) {
|
||||
gm_extended_rx_checks[3].msg[0].addr = (gm_camera_pedal && !gm_camera_pedal_f1) ? 0xBEU : 0xF1U;
|
||||
|
||||
|
||||
static const RxCheck gm_rx_checks[] = {
|
||||
static RxCheck gm_rx_checks[] = {
|
||||
GM_COMMON_RX_CHECKS
|
||||
};
|
||||
#define GM_ASCM_INTERCEPT_RX_CHECKS \
|
||||
@@ -1237,7 +1237,7 @@ static safety_config gm_init(uint16_t safety_param) {
|
||||
{.msg = {{0x1F5, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
static const RxCheck gm_ev_rx_checks[] = {
|
||||
static RxCheck gm_ev_rx_checks[] = {
|
||||
GM_COMMON_RX_CHECKS
|
||||
{.msg = {{0xBD, 0, 7, 40U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
};
|
||||
|
||||
@@ -351,7 +351,7 @@ static safety_config honda_nidec_init(uint16_t param) {
|
||||
// 0x1FA is dynamically forwarded based on stock AEB
|
||||
// 0xE4 is steering on all cars except CRV and RDX, 0x194 for CRV and RDX,
|
||||
// 0x1FA is brake control, 0x30C is acc hud, 0x33D is lkas hud
|
||||
static const CanMsg HONDA_N_TX_MSGS[] = {
|
||||
static CanMsg HONDA_N_TX_MSGS[] = {
|
||||
{0xE4, 0, 5, .check_relay = true},
|
||||
{0x194, 0, 4, .check_relay = true},
|
||||
{0x1FA, 0, 8, .check_relay = false},
|
||||
@@ -426,7 +426,7 @@ static safety_config honda_bosch_init(uint16_t param) {
|
||||
honda_mvl_buttons_fresh = 0U;
|
||||
honda_interceptor_reset(false);
|
||||
// Bosch
|
||||
static const CanMsg HONDA_BOSCH_TX_MSGS[] = {
|
||||
static CanMsg HONDA_BOSCH_TX_MSGS[] = {
|
||||
{0xE4, 0, 5, .check_relay = true},
|
||||
{0xE5, 0, 8, .check_relay = true},
|
||||
{0x296, 1, 4, .check_relay = false},
|
||||
@@ -437,7 +437,7 @@ static safety_config honda_bosch_init(uint16_t param) {
|
||||
};
|
||||
|
||||
// Bosch w/ gas and brakes
|
||||
static const CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {
|
||||
static CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {
|
||||
{0xE4, 1, 5, .check_relay = true},
|
||||
{0x1DF, 1, 8, .check_relay = true},
|
||||
{0x1EF, 1, 8, .check_relay = false},
|
||||
@@ -459,21 +459,21 @@ static safety_config honda_bosch_init(uint16_t param) {
|
||||
};
|
||||
|
||||
// Bosch radarless
|
||||
static const CanMsg HONDA_RADARLESS_TX_MSGS[] = {
|
||||
static CanMsg HONDA_RADARLESS_TX_MSGS[] = {
|
||||
{0xE4, 0, 5, .check_relay = true},
|
||||
{0x296, 2, 4, .check_relay = false},
|
||||
{0x33D, 0, 8, .check_relay = true},
|
||||
};
|
||||
|
||||
// Bosch radarless w/ gas and brakes
|
||||
static const CanMsg HONDA_RADARLESS_LONG_TX_MSGS[] = {
|
||||
static CanMsg HONDA_RADARLESS_LONG_TX_MSGS[] = {
|
||||
{0xE4, 0, 5, .check_relay = true},
|
||||
{0x33D, 0, 8, .check_relay = true},
|
||||
{0x1C8, 0, 8, .check_relay = true},
|
||||
{0x30C, 0, 8, .check_relay = true},
|
||||
};
|
||||
|
||||
static const CanMsg HONDA_CANFD_TX_MSGS[] = {
|
||||
static CanMsg HONDA_CANFD_TX_MSGS[] = {
|
||||
{0xE4, 0, 5, .check_relay = true},
|
||||
{0x296, 0, 4, .check_relay = false},
|
||||
{0x33D, 0, 8, .check_relay = true},
|
||||
|
||||
@@ -92,9 +92,9 @@ static bool honda_stock_aol_sources_current(void) {
|
||||
bool current = false;
|
||||
if ((honda_stock_aol_eps_check() != NULL) && (honda_stock_aol_gear_check() != NULL)) {
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
current = rx_status[honda_stock_aol_eps_index].msg_seen && rx_status[honda_stock_aol_eps_index + 1U].msg_seen &&
|
||||
(safety_get_ts_elapsed(now, rx_status[honda_stock_aol_eps_index].last_timestamp) <= 300000U) &&
|
||||
(safety_get_ts_elapsed(now, rx_status[honda_stock_aol_eps_index + 1U].last_timestamp) <= 300000U) &&
|
||||
current = honda_stock_aol_eps_check()->status.msg_seen && honda_stock_aol_gear_check()->status.msg_seen &&
|
||||
(safety_get_ts_elapsed(now, honda_stock_aol_eps_check()->status.last_timestamp) <= 300000U) &&
|
||||
(safety_get_ts_elapsed(now, honda_stock_aol_gear_check()->status.last_timestamp) <= 300000U) &&
|
||||
(safety_get_ts_elapsed(now, honda_stock_aol_main_ts) <= 300000U) &&
|
||||
(!honda_interceptor || (honda_interceptor_sensor_seen && honda_interceptor_sensor_valid &&
|
||||
(safety_get_ts_elapsed(now, honda_interceptor_sensor_us) <= 200000U)));
|
||||
@@ -149,16 +149,9 @@ static uint8_t honda_stock_aol_permission_mask(void) {
|
||||
|
||||
static bool honda_stock_aol_selected(const CANPacket_t *msg, const RxCheck *check) {
|
||||
bool selected = false;
|
||||
if (check != NULL) {
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
if (check == &honda_rx_workspace[i]) {
|
||||
if (rx_status[i].msg_seen) {
|
||||
const CanMsgCheck *source = &check->msg[rx_status[i].index];
|
||||
selected = msg_matches(msg, source->addr, source->bus, source->len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((check != NULL) && check->status.msg_seen) {
|
||||
const CanMsgCheck *source = &check->msg[check->status.index];
|
||||
selected = msg_matches(msg, source->addr, source->bus, source->len);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
#include "opendbc/safety/modes/hyundai_common.h"
|
||||
#include "opendbc/safety/modes/hyundai_daw.h"
|
||||
#include "opendbc/safety/modes/hyundai_classic_non_scc_aol.h"
|
||||
#include "opendbc/safety/modes/hyundai_blended_stock_aol.h"
|
||||
|
||||
@@ -351,7 +350,7 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
|
||||
const TorqueSteeringLimits HYUNDAI_STEERING_LIMITS_ALT = HYUNDAI_LIMITS(270, 2, 3);
|
||||
const TorqueSteeringLimits HYUNDAI_STEERING_LIMITS_ALT_2 = HYUNDAI_LIMITS(170, 2, 3);
|
||||
|
||||
bool tx = (msg->addr != 0x53EU) || hyundai_daw_tx(msg);
|
||||
bool tx = true;
|
||||
|
||||
if (hyundai_ray_pedal && (msg->addr == 0x200U)) {
|
||||
const uint16_t track1 = ((uint16_t)msg->data[0] << 8U) | msg->data[1];
|
||||
@@ -361,7 +360,7 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
|
||||
bool rx_valid = hyundai_ray_pedal_healthy && !safety_rx_checks_invalid &&
|
||||
(safety_get_ts_elapsed(microsecond_timer_get(), hyundai_ray_pedal_ts) <= 1000000U);
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
const RxStatus *status = &rx_status[i];
|
||||
const RxStatus *status = ¤t_safety_config.rx_checks[i].status;
|
||||
rx_valid &= status->msg_seen && status->valid_checksum && status->valid_quality_flag &&
|
||||
(status->wrong_counters < MAX_WRONG_COUNTERS) && !status->lagging &&
|
||||
(safety_get_ts_elapsed(microsecond_timer_get(), status->last_timestamp) <= 1000000U);
|
||||
@@ -639,7 +638,6 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
};
|
||||
|
||||
hyundai_common_init(param);
|
||||
hyundai_daw_init(false, false);
|
||||
if ((param == 0x800EU) && (alternative_experience == 0)) {
|
||||
hyundai_long_main_policy = HYUNDAI_MAIN_TRACK;
|
||||
}
|
||||
@@ -658,6 +656,9 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
hyundai_blended_hda2 |= (param == 0x2014U);
|
||||
#endif
|
||||
hyundai_blended_stock |= hyundai_blended_alpha;
|
||||
if (hyundai_blended_hda2) {
|
||||
gen_crc_lookup_table_16(0x1021U, hyundai_canfd_crc_lut);
|
||||
}
|
||||
hyundai_ray_pedal = param == 0x9805U;
|
||||
hyundai_ray_pedal_healthy = false;
|
||||
hyundai_ray_pedal_ts = 0U;
|
||||
@@ -759,7 +760,7 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
} else if (hyundai_longitudinal) {
|
||||
// Use CLU11 (buttons) to manage controls allowed instead of SCC cruise state
|
||||
|
||||
static const RxCheck hyundai_fcev_long_rx_checks[] = {
|
||||
static RxCheck hyundai_fcev_long_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(false)
|
||||
HYUNDAI_FCEV_GAS_ADDR_CHECK
|
||||
};
|
||||
@@ -783,7 +784,7 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
}
|
||||
}
|
||||
} else if (hyundai_camera_scc) {
|
||||
static const RxCheck hyundai_cam_scc_rx_checks[] = {
|
||||
static RxCheck hyundai_cam_scc_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(false)
|
||||
HYUNDAI_SCC12_ADDR_CHECK(2)
|
||||
};
|
||||
@@ -795,12 +796,12 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
ret = BUILD_SAFETY_CFG(hyundai_cam_scc_rx_checks, HYUNDAI_CAMERA_SCC_TX_MSGS);
|
||||
}
|
||||
} else {
|
||||
static const RxCheck hyundai_rx_checks[] = {
|
||||
static RxCheck hyundai_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(false)
|
||||
HYUNDAI_SCC12_ADDR_CHECK(0)
|
||||
};
|
||||
|
||||
static const RxCheck hyundai_fcev_rx_checks[] = {
|
||||
static RxCheck hyundai_fcev_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(false)
|
||||
HYUNDAI_SCC12_ADDR_CHECK(0)
|
||||
HYUNDAI_FCEV_GAS_ADDR_CHECK
|
||||
@@ -902,12 +903,12 @@ static safety_config hyundai_init(uint16_t param) {
|
||||
const safety_config denied = {0};
|
||||
ret = denied;
|
||||
}
|
||||
return hyundai_daw_configure(ret, false, !hyundai_blended_stock && !hyundai_blended_alpha);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static safety_config hyundai_legacy_init(uint16_t param) {
|
||||
// older hyundai models have less checks due to missing counters and checksums
|
||||
static const RxCheck hyundai_legacy_rx_checks[] = {
|
||||
static RxCheck hyundai_legacy_rx_checks[] = {
|
||||
HYUNDAI_COMMON_RX_CHECKS(true)
|
||||
HYUNDAI_SCC12_ADDR_CHECK(0)
|
||||
};
|
||||
@@ -924,7 +925,6 @@ static safety_config hyundai_legacy_init(uint16_t param) {
|
||||
}
|
||||
}
|
||||
hyundai_common_init(normalized);
|
||||
hyundai_daw_init(false, false);
|
||||
classic_non_scc_aol_configure(normalized, !legacy_long);
|
||||
hyundai_legacy = true;
|
||||
hyundai_blended_stock = false;
|
||||
@@ -953,11 +953,10 @@ static safety_config hyundai_legacy_init(uint16_t param) {
|
||||
#endif
|
||||
}
|
||||
hyundai_classic_scc_aol_rx_config(&ret, true);
|
||||
return hyundai_daw_configure(ret, false, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void hyundai_optional_rx_hook(const CANPacket_t *msg) {
|
||||
hyundai_daw_rx(msg);
|
||||
if ((msg->addr == 0x391U) || (msg->addr == 0x50CU)) {
|
||||
blended_aol_rx(msg);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
#include "opendbc/safety/modes/hyundai_common.h"
|
||||
#include "opendbc/safety/modes/hyundai_daw.h"
|
||||
#include "opendbc/safety/modes/hyundai_canfd_aol.h"
|
||||
#include "opendbc/safety/modes/hyundai_canfd_angle_aol.h"
|
||||
|
||||
@@ -181,14 +180,11 @@ static bool hyundai_canfd_alt_buttons = false;
|
||||
static bool hyundai_canfd_lka_steer_msg_alt = false;
|
||||
static bool hyundai_canfd_carnival_alt_resume = false;
|
||||
static bool hyundai_canfd_carnival_source_valid = false;
|
||||
static bool hyundai_canfd_carnival_previous_valid = false;
|
||||
static bool hyundai_canfd_carnival_counter_seen = false;
|
||||
static bool hyundai_canfd_carnival_counter_consumed = false;
|
||||
static bool hyundai_canfd_angle_steering = false;
|
||||
static bool hyundai_canfd_angle_observed_adas = false;
|
||||
static bool hyundai_canfd_ioniq6_long = false;
|
||||
static bool aol_ioniq6_long = false;
|
||||
static bool hyundai_stock_torque_aol = false;
|
||||
static bool aol_optional_release_policy = false;
|
||||
static bool aol_optional_release_neutral = false;
|
||||
static uint8_t aol_optional_release_previous = 0U;
|
||||
@@ -208,7 +204,6 @@ static bool aol_ioniq6_lateral_token_claimed = false;
|
||||
static uint32_t aol_ioniq6_gesture_ts = 0U;
|
||||
static bool aol_ioniq6_enable_counter_seen = false;
|
||||
static bool aol_ioniq6_enable_pending = false;
|
||||
static int aol_stock_enable_previous = HYUNDAI_BTN_NONE;
|
||||
static bool aol_ioniq6_enable_neutral = false;
|
||||
static bool aol_ioniq6_enable_release = false;
|
||||
static uint32_t aol_ioniq6_enable_release_ts = 0U;
|
||||
@@ -225,10 +220,6 @@ static bool hyundai_canfd_gv60_heartbeat_seen = false;
|
||||
static uint8_t hyundai_canfd_gv60_heartbeat_counter = 0U;
|
||||
static bool hyundai_canfd_ioniq6_heartbeat_seen = false;
|
||||
static uint8_t hyundai_canfd_ioniq6_heartbeat_counter = 0U;
|
||||
static bool hyundai_canfd_ioniq6_bsm_low_speed = false;
|
||||
static bool hyundai_canfd_ioniq6_bsm_wheel_seen = false;
|
||||
static uint8_t hyundai_canfd_ioniq6_bsm_wheel_counter = 0U;
|
||||
static uint32_t hyundai_canfd_ioniq6_bsm_wheel_ts = 0U;
|
||||
static bool hyundai_canfd_ioniq6_corner_valid = false;
|
||||
static bool hyundai_canfd_ioniq6_corner_counter_seen = false;
|
||||
static uint8_t hyundai_canfd_ioniq6_corner_counter = 0U;
|
||||
@@ -248,7 +239,6 @@ static uint32_t hyundai_canfd_ioniq6_bsm_pair_ts = 0U;
|
||||
static uint8_t hyundai_canfd_angle_model = 0U;
|
||||
static uint8_t hyundai_canfd_carnival_consumed_counter = 0U;
|
||||
static uint8_t hyundai_canfd_carnival_source[16] = {0};
|
||||
static uint8_t hyundai_canfd_carnival_previous[16] = {0};
|
||||
|
||||
// Standard CRUISE_BUTTONS sources use either 0..14 or 0..15. Only the
|
||||
// observed terminal rollover may omit 15; repeats and other gaps stay invalid.
|
||||
@@ -259,7 +249,6 @@ static bool hyundai_canfd_button_counter_follows(uint8_t previous, uint8_t curre
|
||||
|
||||
static void aol_ioniq6_enable_reset(void) {
|
||||
aol_ioniq6_enable_counter_seen = false;
|
||||
aol_stock_enable_previous = HYUNDAI_BTN_NONE;
|
||||
aol_ioniq6_enable_neutral = false;
|
||||
aol_ioniq6_enable_pending = false;
|
||||
aol_ioniq6_enable_release = false;
|
||||
@@ -267,7 +256,6 @@ static void aol_ioniq6_enable_reset(void) {
|
||||
|
||||
static void aol_ioniq6_reset(void) {
|
||||
aol_ioniq6_long = false;
|
||||
hyundai_stock_torque_aol = false;
|
||||
aol_ioniq6_enable_reset();
|
||||
aol_optional_release_capable = false;
|
||||
aol_optional_release_policy = false;
|
||||
@@ -341,9 +329,9 @@ static uint8_t aol_ioniq6_request_mask(void) {
|
||||
aol_host_axis_mask = 0U;
|
||||
} else if (heartbeat_engaged) {
|
||||
aol_ioniq6_session_started = true;
|
||||
if ((hyundai_stock_torque_aol || hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
(aol_optional_release_capable && !aol_optional_release_policy)) && controls_allowed && aol_ioniq6_enable_release &&
|
||||
((aol_host_axis_mask & 0x3U) == (hyundai_stock_torque_aol ? 0x1U : 0x3U)) &&
|
||||
((aol_host_axis_mask & 0x3U) == 0x3U) &&
|
||||
(safety_get_ts_elapsed(now, aol_ioniq6_enable_release_ts) <= AOL_HOST_REQUEST_TIMEOUT_US)) {
|
||||
aol_ioniq6_lateral_latch = true;
|
||||
aol_ioniq6_enable_release = false;
|
||||
@@ -433,17 +421,16 @@ static bool hyundai_canfd_ioniq6_rx_healthy(void) {
|
||||
if (healthy) {
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
const RxCheck *check = ¤t_safety_config.rx_checks[i];
|
||||
const RxStatus *status = &rx_status[i];
|
||||
healthy = status->msg_seen && !status->lagging && status->valid_checksum &&
|
||||
status->valid_quality_flag && (status->wrong_counters < MAX_WRONG_COUNTERS);
|
||||
healthy = check->status.msg_seen && !check->status.lagging && check->status.valid_checksum &&
|
||||
check->status.valid_quality_flag && (check->status.wrong_counters < MAX_WRONG_COUNTERS);
|
||||
if (healthy) {
|
||||
const uint32_t frequency = check->msg[status->index].frequency;
|
||||
const uint32_t frequency = check->msg[check->status.index].frequency;
|
||||
if (frequency < 10U) {
|
||||
healthy = false;
|
||||
} else {
|
||||
const uint32_t period_limit = 10000000U / frequency;
|
||||
const uint32_t max_age = (period_limit > 100000U) ? period_limit : 100000U;
|
||||
healthy = safety_get_ts_elapsed(now, status->last_timestamp) <= max_age;
|
||||
healthy = safety_get_ts_elapsed(now, check->status.last_timestamp) <= max_age;
|
||||
}
|
||||
}
|
||||
if (!healthy) {
|
||||
@@ -454,7 +441,6 @@ static bool hyundai_canfd_ioniq6_rx_healthy(void) {
|
||||
return healthy;
|
||||
}
|
||||
static uint32_t hyundai_canfd_carnival_source_ts = 0U;
|
||||
static uint32_t hyundai_canfd_carnival_previous_ts = 0U;
|
||||
|
||||
static unsigned int hyundai_canfd_get_lka_addr(void) {
|
||||
return hyundai_canfd_lka_steer_msg_alt ? 0x110U : 0x50U;
|
||||
@@ -528,7 +514,6 @@ static void hyundai_canfd_ioniq6_optional_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
static void hyundai_canfd_optional_rx_hook(const CANPacket_t *msg) {
|
||||
hyundai_daw_rx(msg);
|
||||
if (hyundai_canfd_ioniq6_long && (msg->bus == 1U)) {
|
||||
hyundai_canfd_ioniq6_optional_rx_hook(msg);
|
||||
}
|
||||
@@ -600,11 +585,11 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
const bool lkas_button = hyundai_canfd_alt_buttons ? GET_BIT(msg, 39U) : GET_BIT(msg, 23U);
|
||||
const bool previous_controls_allowed = controls_allowed;
|
||||
const int previous_cruise_button = hyundai_stock_torque_aol ? aol_stock_enable_previous : cruise_button_prev;
|
||||
const int previous_cruise_button = cruise_button_prev;
|
||||
const int main_policy = hyundai_canfd_ccnc_ev_long ? (hyundai_canfd_pe_long ? HYUNDAI_MAIN_WITHDRAW : HYUNDAI_MAIN_TRACK) : HYUNDAI_MAIN_NONE;
|
||||
const bool normal_main_button = hyundai_canfd_ccnc_ev_long && GET_BIT(msg, 21U);
|
||||
hyundai_common_cruise_buttons_check(cruise_button, main_button, normal_main_button, main_policy);
|
||||
if ((hyundai_stock_torque_aol || hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
if ((hyundai_canfd_ioniq6_long || hyundai_ev9_long_aol ||
|
||||
(aol_optional_release_capable && !aol_optional_release_policy)) && aol_ioniq6_long) {
|
||||
static uint8_t aol_ioniq6_enable_counter = 0U;
|
||||
static uint32_t aol_ioniq6_enable_ts = 0U;
|
||||
@@ -621,7 +606,7 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
aol_ioniq6_enable_pending = true;
|
||||
} else if (aol_ioniq6_enable_pending && (cruise_button == HYUNDAI_BTN_NONE) &&
|
||||
((previous_cruise_button == HYUNDAI_BTN_SET) || (previous_cruise_button == HYUNDAI_BTN_RESUME))) {
|
||||
if ((controls_allowed || hyundai_stock_torque_aol) && aol_rx_healthy()) {
|
||||
if (controls_allowed && aol_rx_healthy()) {
|
||||
heartbeat_engaged_mismatches = 0U;
|
||||
aol_ioniq6_enable_release = true;
|
||||
aol_ioniq6_enable_release_ts = now;
|
||||
@@ -636,7 +621,6 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
aol_ioniq6_enable_counter_seen = true;
|
||||
aol_ioniq6_enable_counter = counter;
|
||||
aol_ioniq6_enable_ts = now;
|
||||
aol_stock_enable_previous = cruise_button;
|
||||
}
|
||||
if (hyundai_canfd_ccnc_ev_long && !previous_controls_allowed && controls_allowed) {
|
||||
hyundai_ev9_inactive_accel_count = 0U;
|
||||
@@ -706,26 +690,11 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
}
|
||||
if (hyundai_canfd_carnival_alt_resume && (msg_matches(msg, 0x1aaU, pt_bus))) {
|
||||
static uint8_t hyundai_canfd_carnival_rx_counter = 0U;
|
||||
const bool source_fresh = !hyundai_canfd_carnival_counter_seen ||
|
||||
hyundai_canfd_button_counter_follows(hyundai_canfd_carnival_rx_counter, msg->data[2], 0xFFU);
|
||||
hyundai_canfd_carnival_counter_seen = true;
|
||||
hyundai_canfd_carnival_rx_counter = msg->data[2];
|
||||
const bool preceding_valid = source_fresh && hyundai_canfd_carnival_source_valid &&
|
||||
hyundai_canfd_button_counter_follows(hyundai_canfd_carnival_source[2], msg->data[2], 0xFFU);
|
||||
hyundai_canfd_carnival_previous_valid = false;
|
||||
hyundai_canfd_carnival_source_valid = false;
|
||||
const bool idle_buttons = ((msg->data[4] & 0xF4U) == 0U) && ((msg->data[5] & 0x02U) == 0U);
|
||||
if (source_fresh && idle_buttons && ((!hyundai_canfd_carnival_counter_consumed) ||
|
||||
if (idle_buttons && ((!hyundai_canfd_carnival_counter_consumed) ||
|
||||
(msg->data[2] != hyundai_canfd_carnival_consumed_counter)) &&
|
||||
(hyundai_canfd_get_checksum(msg) == (hyundai_common_canfd_compute_checksum(msg) ^ 0x041DU))) {
|
||||
if (preceding_valid) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
hyundai_canfd_carnival_previous[i] = hyundai_canfd_carnival_source[i];
|
||||
}
|
||||
hyundai_canfd_carnival_previous_ts = hyundai_canfd_carnival_source_ts;
|
||||
hyundai_canfd_carnival_previous_valid = true;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
hyundai_canfd_carnival_source[i] = msg->data[i];
|
||||
}
|
||||
@@ -760,18 +729,6 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
|
||||
|
||||
// average of all 4 wheel speeds. Conversion: raw * 0.03125 / 3.6 = m/s
|
||||
UPDATE_VEHICLE_SPEED((fr + rr + rl + fl) / 4.0 * 0.03125 * KPH_TO_MS);
|
||||
if (hyundai_canfd_ioniq6_long) {
|
||||
// Mean wheel speed < 5 mph: raw sum < 2.2352 * 4 * 3.6 / 0.03125.
|
||||
// This hook runs only for valid required RX; TX also checks its freshness.
|
||||
const bool fresh_counter = !hyundai_canfd_ioniq6_bsm_wheel_seen ||
|
||||
(msg->data[2] == (uint8_t)(hyundai_canfd_ioniq6_bsm_wheel_counter + 1U));
|
||||
hyundai_canfd_ioniq6_bsm_low_speed = fresh_counter && ((fr + rr + rl + fl) < 1030U);
|
||||
if (fresh_counter) {
|
||||
hyundai_canfd_ioniq6_bsm_wheel_ts = microsecond_timer_get();
|
||||
}
|
||||
hyundai_canfd_ioniq6_bsm_wheel_seen = true;
|
||||
hyundai_canfd_ioniq6_bsm_wheel_counter = msg->data[2];
|
||||
}
|
||||
}
|
||||
|
||||
// cruise state
|
||||
@@ -1034,15 +991,9 @@ static bool hyundai_canfd_tx_hook_valid(const CANPacket_t *msg) {
|
||||
body[6] = right << 5U;
|
||||
body[7] = 0x80U | (SAFETY_MAX(left, right) << 3U);
|
||||
body[16] = left | (right << 2U);
|
||||
bool physical_body = true;
|
||||
bool neutral_body = hyundai_canfd_ioniq6_bsm_low_speed &&
|
||||
(safety_get_ts_elapsed(now, hyundai_canfd_ioniq6_bsm_wheel_ts) <= 100000U);
|
||||
for (int i = 3; i < 24; i++) {
|
||||
physical_body &= msg->data[i] == body[i];
|
||||
// Only the complete frozen neutral indication may replace a detection.
|
||||
neutral_body &= msg->data[i] == ((i == 7) ? 0x80U : 0U);
|
||||
valid &= msg->data[i] == body[i];
|
||||
}
|
||||
valid &= physical_body || neutral_body;
|
||||
if (valid) {
|
||||
hyundai_canfd_ioniq6_bsm_counter_seen = true;
|
||||
hyundai_canfd_ioniq6_bsm_counter = msg->data[2];
|
||||
@@ -1110,31 +1061,23 @@ static bool hyundai_canfd_tx_hook_valid(const CANPacket_t *msg) {
|
||||
}
|
||||
}
|
||||
if (msg->addr == 0x1aaU) {
|
||||
bool current_match = hyundai_canfd_carnival_source_valid &&
|
||||
safety_get_ts_elapsed(microsecond_timer_get(), hyundai_canfd_carnival_source_ts) <= 100000U &&
|
||||
(msg->data[2] == (uint8_t)(hyundai_canfd_carnival_source[2] + 1U));
|
||||
bool previous_match = hyundai_canfd_carnival_previous_valid &&
|
||||
safety_get_ts_elapsed(microsecond_timer_get(), hyundai_canfd_carnival_previous_ts) <= 100000U &&
|
||||
(msg->data[2] == (uint8_t)(hyundai_canfd_carnival_previous[2] + 1U));
|
||||
bool allowed = hyundai_canfd_carnival_alt_resume && controls_allowed && !safety_rx_checks_invalid &&
|
||||
hyundai_canfd_carnival_source_valid &&
|
||||
safety_get_ts_elapsed(microsecond_timer_get(), hyundai_canfd_carnival_source_ts) <= 100000U &&
|
||||
(msg->data[2] == (uint8_t)(hyundai_canfd_carnival_source[2] + 1U)) &&
|
||||
((msg->data[3] & 0x30U) == 0x10U) && ((msg->data[4] & 0x70U) == 0x10U) &&
|
||||
(hyundai_canfd_get_checksum(msg) == (hyundai_common_canfd_compute_checksum(msg) ^ 0x041DU));
|
||||
for (int i = 3; i < 16; i++) {
|
||||
const uint8_t changed_bits = (i == 3) ? 0x30U : ((i == 4) ? 0x70U : 0U);
|
||||
const uint8_t stable_bits = (uint8_t)~changed_bits;
|
||||
current_match &= ((msg->data[i] & stable_bits) == (hyundai_canfd_carnival_source[i] & stable_bits));
|
||||
previous_match &= ((msg->data[i] & stable_bits) == (hyundai_canfd_carnival_previous[i] & stable_bits));
|
||||
allowed &= ((msg->data[i] & stable_bits) == (hyundai_canfd_carnival_source[i] & stable_bits));
|
||||
}
|
||||
allowed &= current_match || previous_match;
|
||||
if (!allowed) {
|
||||
tx = false;
|
||||
} else {
|
||||
hyundai_canfd_carnival_consumed_counter = current_match ? hyundai_canfd_carnival_source[2] : hyundai_canfd_carnival_previous[2];
|
||||
hyundai_canfd_carnival_consumed_counter = hyundai_canfd_carnival_source[2];
|
||||
hyundai_canfd_carnival_counter_consumed = true;
|
||||
hyundai_canfd_carnival_previous_valid = false;
|
||||
if (current_match) {
|
||||
hyundai_canfd_carnival_source_valid = false;
|
||||
}
|
||||
hyundai_canfd_carnival_source_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,7 +1217,7 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) {
|
||||
}
|
||||
}
|
||||
if (ordinary_angle_valid && !((hyundai_canfd_ioniq6_long || hyundai_canfd_ccnc_ev_long) && safety_rx_checks_invalid)) {
|
||||
tx = (msg->addr == 0x11AU) ? hyundai_daw_tx(msg) : hyundai_canfd_tx_hook_valid(msg);
|
||||
tx = hyundai_canfd_tx_hook_valid(msg);
|
||||
}
|
||||
if ((hyundai_ordinary_angle_stock && !hyundai_angle_aol_enabled && (msg->addr == 0x110U) && tx) ||
|
||||
(independent_command && independent_active && tx)) {
|
||||
@@ -1293,7 +1236,6 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *msg) {
|
||||
static void hyundai_canfd_reset_ioniq6_state(void) {
|
||||
hyundai_canfd_ioniq6_long = false;
|
||||
aol_ioniq6_long = false;
|
||||
hyundai_stock_torque_aol = false;
|
||||
aol_ioniq6_enable_reset();
|
||||
aol_optional_release_capable = false;
|
||||
aol_optional_release_policy = false;
|
||||
@@ -1312,10 +1254,6 @@ static void hyundai_canfd_reset_ioniq6_state(void) {
|
||||
hyundai_canfd_gv60_heartbeat_counter = 0U;
|
||||
hyundai_canfd_ioniq6_heartbeat_seen = false;
|
||||
hyundai_canfd_ioniq6_heartbeat_counter = 0U;
|
||||
hyundai_canfd_ioniq6_bsm_low_speed = false;
|
||||
hyundai_canfd_ioniq6_bsm_wheel_seen = false;
|
||||
hyundai_canfd_ioniq6_bsm_wheel_counter = 0U;
|
||||
hyundai_canfd_ioniq6_bsm_wheel_ts = 0U;
|
||||
hyundai_canfd_ioniq6_corner_valid = false;
|
||||
hyundai_canfd_ioniq6_corner_counter_seen = false;
|
||||
hyundai_canfd_ioniq6_corner_counter = 0U;
|
||||
@@ -1359,15 +1297,13 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
// under torque LKA + LONG. Other uses of the bit retain their original meaning.
|
||||
const uint16_t damping_profile_mask = HYUNDAI_PARAM_CANFD_ANGLE | 16U | HYUNDAI_PARAM_LONGITUDINAL | 8192U;
|
||||
hyundai_canfd_lkas_damping_100 = (param & damping_profile_mask) == (16U | HYUNDAI_PARAM_LONGITUDINAL | 8192U);
|
||||
const bool carnival_long_requested = (param == 0x20B4U) || (param == 0x28B4U);
|
||||
|
||||
if (hyundai_canfd_lkas_damping_100) {
|
||||
param &= (uint16_t)~8192U;
|
||||
}
|
||||
const bool angle_requested = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ANGLE);
|
||||
const bool ioniq6_stock_aol_requested = hyundai_canfd_stock_torque_aol_param(param);
|
||||
#ifdef ALLOW_DEBUG
|
||||
const bool torque_long_aol_requested = ((carnival_long_requested && ((param & 0x0800U) != 0U)) || hyundai_canfd_torque_long_aol_param(param)) && ((unsigned int)alternative_experience == 32U);
|
||||
const bool torque_long_aol_requested = hyundai_canfd_torque_long_aol_param(param) && ((unsigned int)alternative_experience == 32U);
|
||||
#else
|
||||
const bool torque_long_aol_requested = false;
|
||||
#endif
|
||||
@@ -1547,12 +1483,11 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
|
||||
hyundai_common_init(common_param);
|
||||
|
||||
gen_crc_lookup_table_16(0x1021, hyundai_canfd_crc_lut);
|
||||
hyundai_canfd_alt_buttons = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ALT_BUTTONS);
|
||||
hyundai_canfd_carnival_alt_resume = !angle_requested && GET_FLAG(common_param, HYUNDAI_PARAM_CARNIVAL_ALT_RESUME) &&
|
||||
hyundai_canfd_alt_buttons && !GET_FLAG(common_param, HYUNDAI_PARAM_LONGITUDINAL) && !hyundai_longitudinal;
|
||||
hyundai_canfd_carnival_source_valid = false;
|
||||
hyundai_canfd_carnival_previous_valid = false;
|
||||
hyundai_canfd_carnival_counter_seen = false;
|
||||
hyundai_canfd_carnival_counter_consumed = false;
|
||||
hyundai_canfd_carnival_source_ts = 0U;
|
||||
hyundai_canfd_lka_steer_msg_alt = GET_FLAG(param, HYUNDAI_PARAM_CANFD_LKA_STEER_MSG_ALT);
|
||||
@@ -1561,12 +1496,11 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
hyundai_canfd_angle_model = 0U;
|
||||
#ifdef ALLOW_DEBUG
|
||||
hyundai_canfd_ioniq6_long = ioniq6_long_requested;
|
||||
hyundai_canfd_torque_ev_long = (param == 0x0015U) || (param == 0x0095U) || (torque_long_aol_requested && GET_FLAG(param, 16U) && !carnival_long_requested) || gv60_long_requested;
|
||||
hyundai_canfd_torque_ev_long = (param == 0x0015U) || (param == 0x0095U) || (torque_long_aol_requested && GET_FLAG(param, 16U)) || gv60_long_requested;
|
||||
#endif
|
||||
hyundai_torque_aol_cancel_preserves_lateral = ioniq6_stock_aol_requested || torque_long_aol_requested ||
|
||||
(param == 0x8815U) || (param == 0x8895U);
|
||||
aol_optional_release_capable = torque_long_aol_requested;
|
||||
hyundai_stock_torque_aol = ioniq6_stock_aol_requested;
|
||||
aol_ioniq6_long = ioniq6_stock_aol_requested || torque_long_aol_requested || (param == 0x8815U) || (param == 0x8895U);
|
||||
if (aol_ioniq6_long) {
|
||||
static const AolSafetyPolicy aol_ioniq6_policy = {
|
||||
@@ -1811,38 +1745,25 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
#endif
|
||||
if (hyundai_longitudinal) {
|
||||
if (hyundai_canfd_lka_steer_msg) {
|
||||
static const RxCheck hyundai_canfd_lka_steer_msg_long_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_lka_steer_msg_long_rx_checks[] = {
|
||||
HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(1)
|
||||
};
|
||||
|
||||
static const RxCheck hyundai_canfd_lka_alt_buttons_long_rx_checks[] = {
|
||||
HYUNDAI_CANFD_ALT_BUTTONS_RX_CHECKS(1)
|
||||
};
|
||||
static const RxCheck carnival_long_rx_checks[] = {
|
||||
HYUNDAI_CANFD_BANKED_ANGLE_ALT_RX_CHECKS(1, 0x100)
|
||||
};
|
||||
ret = BUILD_SAFETY_CFG(hyundai_canfd_lka_steer_msg_long_rx_checks, HYUNDAI_CANFD_LKA_STEER_MSG_LONG_TX_MSGS);
|
||||
if (carnival_long_requested) {
|
||||
SET_RX_CHECKS(carnival_long_rx_checks, ret);
|
||||
} else if (hyundai_canfd_alt_buttons) {
|
||||
SET_RX_CHECKS(hyundai_canfd_lka_alt_buttons_long_rx_checks, ret);
|
||||
} else {
|
||||
// Standard-button profiles retain their existing physical source.
|
||||
}
|
||||
if (hyundai_canfd_lka_steer_msg_alt) {
|
||||
SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEER_MSG_ALT_LONG_TX_MSGS, ret);
|
||||
}
|
||||
} else {
|
||||
// Longitudinal checks for LFA steering
|
||||
static const RxCheck hyundai_canfd_long_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_long_rx_checks[] = {
|
||||
HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(0)
|
||||
};
|
||||
|
||||
static const RxCheck hyundai_canfd_alt_buttons_long_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_alt_buttons_long_rx_checks[] = {
|
||||
HYUNDAI_CANFD_ALT_BUTTONS_RX_CHECKS(0)
|
||||
};
|
||||
|
||||
static const CanMsg hyundai_canfd_lfa_steering_camera_scc_tx_msgs[] = {
|
||||
static CanMsg hyundai_canfd_lfa_steering_camera_scc_tx_msgs[] = {
|
||||
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_TX_MSGS(true)
|
||||
};
|
||||
static CanMsg hyundai_canfd_ccnc_camera_scc_tx_msgs[] = {
|
||||
@@ -1870,7 +1791,7 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
// *** LKA steering checks ***
|
||||
// E-CAN is on bus 1, SCC messages are sent on cars with ADRV ECU.
|
||||
// Does not use the alt buttons message
|
||||
static const RxCheck hyundai_canfd_lka_steer_msg_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_lka_steer_msg_rx_checks[] = {
|
||||
HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(1)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
|
||||
};
|
||||
@@ -1899,12 +1820,12 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
}
|
||||
} else if (!hyundai_camera_scc) {
|
||||
// Radar sends SCC messages on these cars instead of camera
|
||||
static const RxCheck hyundai_canfd_radar_scc_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_radar_scc_rx_checks[] = {
|
||||
HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(0)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
|
||||
static const RxCheck hyundai_canfd_alt_buttons_radar_scc_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_alt_buttons_radar_scc_rx_checks[] = {
|
||||
HYUNDAI_CANFD_ALT_BUTTONS_RX_CHECKS(0)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
|
||||
};
|
||||
@@ -1920,17 +1841,17 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
// *** LFA steering checks ***
|
||||
// Camera sends SCC messages on LFA steering cars.
|
||||
// Both button messages exist on some platforms, so we ensure we track the correct one using flag
|
||||
static const RxCheck hyundai_canfd_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_rx_checks[] = {
|
||||
HYUNDAI_CANFD_STD_BUTTONS_RX_CHECKS(0)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
|
||||
static const RxCheck hyundai_canfd_alt_buttons_rx_checks[] = {
|
||||
static RxCheck hyundai_canfd_alt_buttons_rx_checks[] = {
|
||||
HYUNDAI_CANFD_ALT_BUTTONS_RX_CHECKS(0)
|
||||
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
|
||||
};
|
||||
|
||||
static const CanMsg hyundai_canfd_lfa_steering_camera_scc_tx_msgs[] = {
|
||||
static CanMsg hyundai_canfd_lfa_steering_camera_scc_tx_msgs[] = {
|
||||
HYUNDAI_CANFD_LFA_STEERING_CAMERA_SCC_TX_MSGS(false)
|
||||
};
|
||||
static CanMsg hyundai_canfd_ccnc_camera_scc_tx_msgs[] = {
|
||||
@@ -2238,7 +2159,6 @@ static safety_config hyundai_canfd_init_validated(uint16_t supplied_param) {
|
||||
}
|
||||
|
||||
static safety_config hyundai_canfd_init(uint16_t param) {
|
||||
hyundai_daw_init(true, false);
|
||||
hyundai_canfd_reset_ioniq6_state();
|
||||
hyundai_ordinary_angle_reset();
|
||||
#ifdef ALLOW_DEBUG
|
||||
@@ -2257,9 +2177,6 @@ static safety_config hyundai_canfd_init(uint16_t param) {
|
||||
(param == 0x8815U) || (param == 0x8895U);
|
||||
if (valid && marker_valid) {
|
||||
ret = hyundai_canfd_init_validated(param);
|
||||
const bool daw_profile = (param == 0x0011U) || (param == 0x0091U) || (param == 0x0811U) || (param == 0x0891U) ||
|
||||
(param == 0x8015U) || (param == 0x8095U) || (param == 0x8815U) || (param == 0x8895U);
|
||||
ret = hyundai_daw_configure(ret, true, daw_profile);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ static inline bool hyundai_canfd_torque_long_aol_param(uint16_t param) {
|
||||
((base & 4U) != 0U) && ((fuel == 0U) || (fuel == 2U)) &&
|
||||
((topology == 0U) || (topology == 8U) || (topology == 16U) ||
|
||||
(topology == 144U) || (topology == 32U) || (topology == 40U));
|
||||
return (param == 0x28B4U) || sportage || (param == 0x080DU) || (param == 0x0815U) || (param == 0x0895U) || (param == 0x2815U) || (param == 0x2895U);
|
||||
return sportage || (param == 0x080DU) || (param == 0x0815U) || (param == 0x0895U) || (param == 0x2815U) || (param == 0x2895U);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,33 +2,8 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
|
||||
// CRC-16 lookup table for poly 0x1021 (CRC-16/XMODEM)
|
||||
// generated with: python3 -c 'f=lambda c:[c:=((c<<1)^0x1021)&0xFFFF if c&0x8000 else (c<<1)&0xFFFF for _ in range(8)][-1];print(",\n".join(" "+", ".join(f"0x{f(i<<8):04X}U" for i in range(r,min(r+12,256))) for r in range(0,256,12)))'
|
||||
extern const uint16_t hyundai_canfd_crc_lut[256];
|
||||
const uint16_t hyundai_canfd_crc_lut[256] = {
|
||||
0x0000U, 0x1021U, 0x2042U, 0x3063U, 0x4084U, 0x50A5U, 0x60C6U, 0x70E7U, 0x8108U, 0x9129U, 0xA14AU, 0xB16BU,
|
||||
0xC18CU, 0xD1ADU, 0xE1CEU, 0xF1EFU, 0x1231U, 0x0210U, 0x3273U, 0x2252U, 0x52B5U, 0x4294U, 0x72F7U, 0x62D6U,
|
||||
0x9339U, 0x8318U, 0xB37BU, 0xA35AU, 0xD3BDU, 0xC39CU, 0xF3FFU, 0xE3DEU, 0x2462U, 0x3443U, 0x0420U, 0x1401U,
|
||||
0x64E6U, 0x74C7U, 0x44A4U, 0x5485U, 0xA56AU, 0xB54BU, 0x8528U, 0x9509U, 0xE5EEU, 0xF5CFU, 0xC5ACU, 0xD58DU,
|
||||
0x3653U, 0x2672U, 0x1611U, 0x0630U, 0x76D7U, 0x66F6U, 0x5695U, 0x46B4U, 0xB75BU, 0xA77AU, 0x9719U, 0x8738U,
|
||||
0xF7DFU, 0xE7FEU, 0xD79DU, 0xC7BCU, 0x48C4U, 0x58E5U, 0x6886U, 0x78A7U, 0x0840U, 0x1861U, 0x2802U, 0x3823U,
|
||||
0xC9CCU, 0xD9EDU, 0xE98EU, 0xF9AFU, 0x8948U, 0x9969U, 0xA90AU, 0xB92BU, 0x5AF5U, 0x4AD4U, 0x7AB7U, 0x6A96U,
|
||||
0x1A71U, 0x0A50U, 0x3A33U, 0x2A12U, 0xDBFDU, 0xCBDCU, 0xFBBFU, 0xEB9EU, 0x9B79U, 0x8B58U, 0xBB3BU, 0xAB1AU,
|
||||
0x6CA6U, 0x7C87U, 0x4CE4U, 0x5CC5U, 0x2C22U, 0x3C03U, 0x0C60U, 0x1C41U, 0xEDAEU, 0xFD8FU, 0xCDECU, 0xDDCDU,
|
||||
0xAD2AU, 0xBD0BU, 0x8D68U, 0x9D49U, 0x7E97U, 0x6EB6U, 0x5ED5U, 0x4EF4U, 0x3E13U, 0x2E32U, 0x1E51U, 0x0E70U,
|
||||
0xFF9FU, 0xEFBEU, 0xDFDDU, 0xCFFCU, 0xBF1BU, 0xAF3AU, 0x9F59U, 0x8F78U, 0x9188U, 0x81A9U, 0xB1CAU, 0xA1EBU,
|
||||
0xD10CU, 0xC12DU, 0xF14EU, 0xE16FU, 0x1080U, 0x00A1U, 0x30C2U, 0x20E3U, 0x5004U, 0x4025U, 0x7046U, 0x6067U,
|
||||
0x83B9U, 0x9398U, 0xA3FBU, 0xB3DAU, 0xC33DU, 0xD31CU, 0xE37FU, 0xF35EU, 0x02B1U, 0x1290U, 0x22F3U, 0x32D2U,
|
||||
0x4235U, 0x5214U, 0x6277U, 0x7256U, 0xB5EAU, 0xA5CBU, 0x95A8U, 0x8589U, 0xF56EU, 0xE54FU, 0xD52CU, 0xC50DU,
|
||||
0x34E2U, 0x24C3U, 0x14A0U, 0x0481U, 0x7466U, 0x6447U, 0x5424U, 0x4405U, 0xA7DBU, 0xB7FAU, 0x8799U, 0x97B8U,
|
||||
0xE75FU, 0xF77EU, 0xC71DU, 0xD73CU, 0x26D3U, 0x36F2U, 0x0691U, 0x16B0U, 0x6657U, 0x7676U, 0x4615U, 0x5634U,
|
||||
0xD94CU, 0xC96DU, 0xF90EU, 0xE92FU, 0x99C8U, 0x89E9U, 0xB98AU, 0xA9ABU, 0x5844U, 0x4865U, 0x7806U, 0x6827U,
|
||||
0x18C0U, 0x08E1U, 0x3882U, 0x28A3U, 0xCB7DU, 0xDB5CU, 0xEB3FU, 0xFB1EU, 0x8BF9U, 0x9BD8U, 0xABBBU, 0xBB9AU,
|
||||
0x4A75U, 0x5A54U, 0x6A37U, 0x7A16U, 0x0AF1U, 0x1AD0U, 0x2AB3U, 0x3A92U, 0xFD2EU, 0xED0FU, 0xDD6CU, 0xCD4DU,
|
||||
0xBDAAU, 0xAD8BU, 0x9DE8U, 0x8DC9U, 0x7C26U, 0x6C07U, 0x5C64U, 0x4C45U, 0x3CA2U, 0x2C83U, 0x1CE0U, 0x0CC1U,
|
||||
0xEF1FU, 0xFF3EU, 0xCF5DU, 0xDF7CU, 0xAF9BU, 0xBFBAU, 0x8FD9U, 0x9FF8U, 0x6E17U, 0x7E36U, 0x4E55U, 0x5E74U,
|
||||
0x2E93U, 0x3EB2U, 0x0ED1U, 0x1EF0U
|
||||
};
|
||||
extern uint16_t hyundai_canfd_crc_lut[256];
|
||||
uint16_t hyundai_canfd_crc_lut[256];
|
||||
|
||||
static const uint8_t HYUNDAI_PREV_BUTTON_SAMPLES = 8; // roughly 160 ms
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "opendbc/safety/modes/hyundai_common.h"
|
||||
|
||||
static uint16_t hyundai_daw_addr;
|
||||
static unsigned int hyundai_daw_bus;
|
||||
static unsigned int hyundai_daw_len;
|
||||
static uint8_t hyundai_daw_data[16];
|
||||
static uint8_t hyundai_daw_previous[16];
|
||||
static bool hyundai_daw_previous_seen;
|
||||
static uint32_t hyundai_daw_previous_ts;
|
||||
static uint32_t hyundai_daw_rx_ts;
|
||||
static uint32_t hyundai_daw_tx_ts;
|
||||
static bool hyundai_daw_seen;
|
||||
static bool hyundai_daw_counter_seen;
|
||||
static bool hyundai_daw_sent;
|
||||
|
||||
static void hyundai_daw_init(bool canfd, bool enabled) {
|
||||
hyundai_daw_addr = enabled ? (canfd ? 0x11AU : 0x53EU) : 0U;
|
||||
hyundai_daw_bus = canfd ? 1U : 0U;
|
||||
hyundai_daw_len = canfd ? 16U : 6U;
|
||||
hyundai_daw_previous_seen = false;
|
||||
hyundai_daw_previous_ts = 0U;
|
||||
hyundai_daw_seen = false;
|
||||
hyundai_daw_counter_seen = false;
|
||||
hyundai_daw_sent = false;
|
||||
hyundai_daw_rx_ts = 0U;
|
||||
hyundai_daw_tx_ts = 0U;
|
||||
}
|
||||
|
||||
static void hyundai_daw_rx(const CANPacket_t *msg) {
|
||||
const bool canfd = hyundai_daw_len == 16U;
|
||||
if ((hyundai_daw_addr != 0U) && (msg->addr == hyundai_daw_addr) && (msg->bus == (canfd ? hyundai_daw_bus : 2U))) {
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
const bool fresh = hyundai_daw_counter_seen && (safety_get_ts_elapsed(now, hyundai_daw_rx_ts) <= 100000U);
|
||||
bool valid = GET_LEN(msg) == hyundai_daw_len;
|
||||
if (valid && canfd) {
|
||||
valid = GET_BYTES_LE(msg, 0, 2) == (hyundai_common_canfd_compute_checksum(msg) ^ 0x041DU);
|
||||
if (fresh) {
|
||||
valid &= (uint8_t)(msg->data[2] - hyundai_daw_data[2]) == 1U;
|
||||
}
|
||||
}
|
||||
hyundai_daw_previous_seen = canfd && valid && fresh && hyundai_daw_seen;
|
||||
if (hyundai_daw_previous_seen) {
|
||||
(void)memcpy(hyundai_daw_previous, hyundai_daw_data, hyundai_daw_len);
|
||||
hyundai_daw_previous_ts = hyundai_daw_rx_ts;
|
||||
}
|
||||
hyundai_daw_seen = valid;
|
||||
if (valid) {
|
||||
(void)memcpy(hyundai_daw_data, msg->data, hyundai_daw_len);
|
||||
hyundai_daw_rx_ts = now;
|
||||
hyundai_daw_counter_seen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t hyundai_daw_expected(const uint8_t *data, unsigned int i) {
|
||||
uint8_t expected = data[i];
|
||||
if ((hyundai_daw_len == 16U) && (i == 7U) && (((expected >> 3U) & 7U) == 1U)) {
|
||||
expected &= (uint8_t)~0x38U;
|
||||
} else if ((hyundai_daw_len == 6U) && (i == 5U) && ((expected & 7U) >= 1U) && ((expected & 7U) <= 3U)) {
|
||||
expected &= (uint8_t)~7U;
|
||||
} else {
|
||||
// Non-advisory payload bits are copied exactly from the current source.
|
||||
}
|
||||
return expected;
|
||||
}
|
||||
|
||||
static bool hyundai_daw_tx(const CANPacket_t *msg) {
|
||||
const uint32_t now = microsecond_timer_get();
|
||||
const bool canfd = hyundai_daw_len == 16U;
|
||||
bool valid = hyundai_daw_seen && (hyundai_daw_addr != 0U) && (msg->addr == hyundai_daw_addr) &&
|
||||
(msg->bus == hyundai_daw_bus) && (GET_LEN(msg) == hyundai_daw_len) &&
|
||||
(safety_get_ts_elapsed(now, hyundai_daw_rx_ts) <= 100000U) &&
|
||||
(!hyundai_daw_sent || (safety_get_ts_elapsed(now, hyundai_daw_tx_ts) >= 5000U));
|
||||
if (valid) {
|
||||
const uint8_t *source = hyundai_daw_data;
|
||||
if (canfd && (msg->data[2] != hyundai_daw_data[2])) {
|
||||
valid = hyundai_daw_previous_seen && (msg->data[2] == hyundai_daw_previous[2]) &&
|
||||
(safety_get_ts_elapsed(now, hyundai_daw_previous_ts) <= 100000U);
|
||||
if (valid) {
|
||||
// A one-frame receive race cannot replace newer warning or status data.
|
||||
for (unsigned int i = 3U; i < hyundai_daw_len; i++) {
|
||||
valid &= hyundai_daw_expected(hyundai_daw_previous, i) == hyundai_daw_expected(hyundai_daw_data, i);
|
||||
}
|
||||
source = hyundai_daw_previous;
|
||||
}
|
||||
}
|
||||
for (unsigned int i = canfd ? 2U : 0U; i < hyundai_daw_len; i++) {
|
||||
valid &= msg->data[i] == hyundai_daw_expected(source, i);
|
||||
}
|
||||
if (canfd) {
|
||||
valid &= GET_BYTES_LE(msg, 0, 2) == (hyundai_common_canfd_compute_checksum(msg) ^ 0x041DU);
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
hyundai_daw_sent = true;
|
||||
hyundai_daw_tx_ts = now;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
static safety_config hyundai_daw_configure(safety_config ret, bool canfd, bool enabled) {
|
||||
hyundai_daw_init(canfd, false);
|
||||
if (enabled && (ret.tx_msgs_len > 0) && (ret.tx_msgs_len < 64)) {
|
||||
static CanMsg daw_tx_msgs[64];
|
||||
for (int i = 0; i < ret.tx_msgs_len; i++) {
|
||||
daw_tx_msgs[i] = ret.tx_msgs[i];
|
||||
}
|
||||
const CanMsg daw = {canfd ? 0x11AU : 0x53EU, canfd ? 1U : 0U, canfd ? 16U : 6U, .check_relay = false};
|
||||
daw_tx_msgs[ret.tx_msgs_len] = daw;
|
||||
ret.tx_msgs_len++;
|
||||
ret.tx_msgs = daw_tx_msgs;
|
||||
hyundai_daw_init(canfd, true);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ static safety_config mazda_init(uint16_t param) {
|
||||
{MAZDA_LKAS_HUD, 0, 8, .check_relay = true},
|
||||
};
|
||||
|
||||
static const RxCheck mazda_rx_checks[] = {
|
||||
static RxCheck mazda_rx_checks[] = {
|
||||
{.msg = {{MAZDA_CRZ_CTRL, 0, 8, 50U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{MAZDA_CRZ_BTNS, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
{.msg = {{MAZDA_STEER_TORQUE, 0, 8, 83U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}},
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "opendbc/safety/declarations.h"
|
||||
|
||||
static uint8_t mg_crc_lut[256];
|
||||
|
||||
static uint32_t mg_get_checksum(const CANPacket_t *msg) {
|
||||
uint32_t checksum = msg->data[7];
|
||||
if (msg->addr == 0x1ecU) {
|
||||
@@ -11,26 +13,6 @@ static uint32_t mg_get_checksum(const CANPacket_t *msg) {
|
||||
}
|
||||
|
||||
static uint32_t mg_compute_checksum(const CANPacket_t *msg) {
|
||||
// CRC-8 lookup table for poly 0x1D (SAE J1850)
|
||||
// generated with: python3 -c 'f=lambda c:[c:=((c<<1)^0x1D)&0xFF if c&0x80 else (c<<1)&0xFF for _ in range(8)][-1];print(",\n".join(" "+", ".join(f"0x{f(i):02X}U" for i in range(r,r+16)) for r in range(0,256,16)))'
|
||||
static const uint8_t mg_crc_lut[256] = {
|
||||
0x00U, 0x1DU, 0x3AU, 0x27U, 0x74U, 0x69U, 0x4EU, 0x53U, 0xE8U, 0xF5U, 0xD2U, 0xCFU, 0x9CU, 0x81U, 0xA6U, 0xBBU,
|
||||
0xCDU, 0xD0U, 0xF7U, 0xEAU, 0xB9U, 0xA4U, 0x83U, 0x9EU, 0x25U, 0x38U, 0x1FU, 0x02U, 0x51U, 0x4CU, 0x6BU, 0x76U,
|
||||
0x87U, 0x9AU, 0xBDU, 0xA0U, 0xF3U, 0xEEU, 0xC9U, 0xD4U, 0x6FU, 0x72U, 0x55U, 0x48U, 0x1BU, 0x06U, 0x21U, 0x3CU,
|
||||
0x4AU, 0x57U, 0x70U, 0x6DU, 0x3EU, 0x23U, 0x04U, 0x19U, 0xA2U, 0xBFU, 0x98U, 0x85U, 0xD6U, 0xCBU, 0xECU, 0xF1U,
|
||||
0x13U, 0x0EU, 0x29U, 0x34U, 0x67U, 0x7AU, 0x5DU, 0x40U, 0xFBU, 0xE6U, 0xC1U, 0xDCU, 0x8FU, 0x92U, 0xB5U, 0xA8U,
|
||||
0xDEU, 0xC3U, 0xE4U, 0xF9U, 0xAAU, 0xB7U, 0x90U, 0x8DU, 0x36U, 0x2BU, 0x0CU, 0x11U, 0x42U, 0x5FU, 0x78U, 0x65U,
|
||||
0x94U, 0x89U, 0xAEU, 0xB3U, 0xE0U, 0xFDU, 0xDAU, 0xC7U, 0x7CU, 0x61U, 0x46U, 0x5BU, 0x08U, 0x15U, 0x32U, 0x2FU,
|
||||
0x59U, 0x44U, 0x63U, 0x7EU, 0x2DU, 0x30U, 0x17U, 0x0AU, 0xB1U, 0xACU, 0x8BU, 0x96U, 0xC5U, 0xD8U, 0xFFU, 0xE2U,
|
||||
0x26U, 0x3BU, 0x1CU, 0x01U, 0x52U, 0x4FU, 0x68U, 0x75U, 0xCEU, 0xD3U, 0xF4U, 0xE9U, 0xBAU, 0xA7U, 0x80U, 0x9DU,
|
||||
0xEBU, 0xF6U, 0xD1U, 0xCCU, 0x9FU, 0x82U, 0xA5U, 0xB8U, 0x03U, 0x1EU, 0x39U, 0x24U, 0x77U, 0x6AU, 0x4DU, 0x50U,
|
||||
0xA1U, 0xBCU, 0x9BU, 0x86U, 0xD5U, 0xC8U, 0xEFU, 0xF2U, 0x49U, 0x54U, 0x73U, 0x6EU, 0x3DU, 0x20U, 0x07U, 0x1AU,
|
||||
0x6CU, 0x71U, 0x56U, 0x4BU, 0x18U, 0x05U, 0x22U, 0x3FU, 0x84U, 0x99U, 0xBEU, 0xA3U, 0xF0U, 0xEDU, 0xCAU, 0xD7U,
|
||||
0x35U, 0x28U, 0x0FU, 0x12U, 0x41U, 0x5CU, 0x7BU, 0x66U, 0xDDU, 0xC0U, 0xE7U, 0xFAU, 0xA9U, 0xB4U, 0x93U, 0x8EU,
|
||||
0xF8U, 0xE5U, 0xC2U, 0xDFU, 0x8CU, 0x91U, 0xB6U, 0xABU, 0x10U, 0x0DU, 0x2AU, 0x37U, 0x64U, 0x79U, 0x5EU, 0x43U,
|
||||
0xB2U, 0xAFU, 0x88U, 0x95U, 0xC6U, 0xDBU, 0xFCU, 0xE1U, 0x5AU, 0x47U, 0x60U, 0x7DU, 0x2EU, 0x33U, 0x14U, 0x09U,
|
||||
0x7FU, 0x62U, 0x45U, 0x58U, 0x0BU, 0x16U, 0x31U, 0x2CU, 0x97U, 0x8AU, 0xADU, 0xB0U, 0xE3U, 0xFEU, 0xD9U, 0xC4U
|
||||
};
|
||||
uint32_t checksum;
|
||||
if (msg->addr == 0x1ecU) {
|
||||
uint32_t torque = ((msg->data[0] & 0x7U) << 8) | msg->data[1];
|
||||
@@ -122,9 +104,11 @@ static bool mg_tx_hook(const CANPacket_t *msg) {
|
||||
static safety_config mg_init(uint16_t param) {
|
||||
SAFETY_UNUSED(param);
|
||||
|
||||
gen_crc_lookup_table_8(0x1DU, mg_crc_lut);
|
||||
|
||||
static const CanMsg MG_TX_MSGS[] = {{0x1fd, 0, 8, .check_relay = true}};
|
||||
|
||||
static const RxCheck mg_rx_checks[] = {
|
||||
static RxCheck mg_rx_checks[] = {
|
||||
{.msg = {{0x23c, 0, 8, .frequency = 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // SCS_HSC2_FrP19 (speed)
|
||||
{.msg = {{0xaf, 0, 8, .frequency = 100U, .ignore_checksum = true, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // GW_HSC2_HCU_FrP00 (gas pedal)
|
||||
{.msg = {{0x1ec, 0, 8, .frequency = 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // EPS_HSC2_FrP03 (driver torque)
|
||||
|
||||
@@ -109,7 +109,7 @@ static safety_config psa_init(uint16_t param) {
|
||||
{PSA_LANE_KEEP_ASSIST, PSA_MAIN_BUS, 8, .check_relay = true}, // EPS steering
|
||||
};
|
||||
|
||||
static const RxCheck psa_rx_checks[] = {
|
||||
static RxCheck psa_rx_checks[] = {
|
||||
{.msg = {{PSA_HS2_DAT_MDD_CMD_452, PSA_ADAS_BUS, 6, 20U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // cruise state
|
||||
{.msg = {{PSA_HS2_DYN_ABR_38D, PSA_MAIN_BUS, 8, 25U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // speed
|
||||
{.msg = {{PSA_STEERING_ALT, PSA_MAIN_BUS, 7, 100U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, // steering angle
|
||||
|
||||
@@ -118,9 +118,8 @@ static bool subaru_avh_tx(const CANPacket_t *msg) {
|
||||
// Rejected required wheel-speed input must also invalidate its cached source.
|
||||
for (int i = 0; i < current_safety_config.rx_checks_len; i++) {
|
||||
const RxCheck *check = ¤t_safety_config.rx_checks[i];
|
||||
const RxStatus *status = &rx_status[i];
|
||||
if (((unsigned int)check->msg[status->index].addr == 0x13AU) && (check->msg[status->index].bus == 1U)) {
|
||||
allowed &= status->valid_checksum && (status->wrong_counters < MAX_WRONG_COUNTERS);
|
||||
if (((unsigned int)check->msg[check->status.index].addr == 0x13AU) && (check->msg[check->status.index].bus == 1U)) {
|
||||
allowed &= check->status.valid_checksum && (check->status.wrong_counters < MAX_WRONG_COUNTERS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user